From 2702905cfdc5738c3202e12a5e79a55d0be6cbfc Mon Sep 17 00:00:00 2001 From: voidlizard Date: Mon, 7 Oct 2024 05:03:46 +0300 Subject: [PATCH] Squashed 'miscellaneous/saltine/' content from commit 6930947c5 git-subtree-dir: miscellaneous/saltine git-subtree-split: 6930947c556970daf8bdac71f9bdc3bb592b80c9 --- .github/workflows/haskell.yml | 53 ++ .gitignore | 2 + CHANGELOG.md | 44 ++ LICENSE | 21 + Makefile | 20 + README.md | 69 +++ Setup.hs | 3 + bench/AES256GCMBench.hs | 68 +++ bench/AuthBench.hs | 37 ++ bench/BenchUtils.hs | 15 + bench/BoxBench.hs | 45 ++ bench/ChaCha20Poly1305Bench.hs | 68 +++ bench/ChaCha20Poly1305IETFBench.hs | 68 +++ bench/ConstantTimeBench.hs | 29 + bench/HashBench.hs | 40 ++ bench/Main.hs | 79 +++ bench/OneTimeAuthBench.hs | 37 ++ bench/PasswordBench.hs | 87 +++ bench/RandomBench.hs | 21 + bench/ScalarMultBench.hs | 35 ++ bench/SecretBoxBench.hs | 57 ++ bench/SignBench.hs | 53 ++ bench/StreamBench.hs | 37 ++ bench/XChaCha20Poly1305Bench.hs | 68 +++ saltine.cabal | 159 +++++ src/Crypto/Saltine.hs | 23 + src/Crypto/Saltine/Class.hs | 55 ++ src/Crypto/Saltine/Core/AEAD.hs | 47 ++ src/Crypto/Saltine/Core/AEAD/AES256GCM.hs | 149 +++++ .../Saltine/Core/AEAD/ChaCha20Poly1305.hs | 134 +++++ .../Saltine/Core/AEAD/ChaCha20Poly1305IETF.hs | 134 +++++ .../Saltine/Core/AEAD/XChaCha20Poly1305.hs | 134 +++++ src/Crypto/Saltine/Core/Auth.hs | 84 +++ src/Crypto/Saltine/Core/Box.hs | 231 +++++++ src/Crypto/Saltine/Core/Hash.hs | 103 ++++ src/Crypto/Saltine/Core/OneTimeAuth.hs | 80 +++ src/Crypto/Saltine/Core/Password.hs | 247 ++++++++ src/Crypto/Saltine/Core/ScalarMult.hs | 79 +++ src/Crypto/Saltine/Core/SecretBox.hs | 146 +++++ src/Crypto/Saltine/Core/Sign.hs | 131 ++++ src/Crypto/Saltine/Core/Stream.hs | 103 ++++ src/Crypto/Saltine/Core/Utils.hs | 6 + src/Crypto/Saltine/Internal/AEAD/AES256GCM.hs | 188 ++++++ .../Saltine/Internal/AEAD/ChaCha20Poly1305.hs | 184 ++++++ .../Internal/AEAD/ChaCha20Poly1305IETF.hs | 183 ++++++ .../Internal/AEAD/XChaCha20Poly1305.hs | 183 ++++++ src/Crypto/Saltine/Internal/Auth.hs | 103 ++++ src/Crypto/Saltine/Internal/Box.hs | 285 +++++++++ src/Crypto/Saltine/Internal/ByteSizes.hs | 193 ++++++ src/Crypto/Saltine/Internal/Hash.hs | 162 +++++ src/Crypto/Saltine/Internal/OneTimeAuth.hs | 104 ++++ src/Crypto/Saltine/Internal/Password.hs | 567 ++++++++++++++++++ src/Crypto/Saltine/Internal/ScalarMult.hs | 91 +++ src/Crypto/Saltine/Internal/SecretBox.hs | 179 ++++++ src/Crypto/Saltine/Internal/Sign.hs | 182 ++++++ src/Crypto/Saltine/Internal/Stream.hs | 107 ++++ src/Crypto/Saltine/Internal/Util.hs | 188 ++++++ tests/AEAD/AES256GCMProperties.hs | 125 ++++ tests/AEAD/ChaCha20Poly1305IETFProperties.hs | 128 ++++ tests/AEAD/ChaCha20Poly1305Properties.hs | 128 ++++ tests/AEAD/XChaCha20Poly1305Properties.hs | 128 ++++ tests/AuthProperties.hs | 21 + tests/BoxProperties.hs | 110 ++++ tests/HashProperties.hs | 46 ++ tests/Main.hs | 51 ++ tests/OneTimeAuthProperties.hs | 22 + tests/PasswordProperties.hs | 97 +++ tests/ScalarMultProperties.hs | 74 +++ tests/SealedBoxProperties.hs | 71 +++ tests/SecretBoxProperties.hs | 116 ++++ tests/SignProperties.hs | 43 ++ tests/StreamProperties.hs | 32 + tests/Util.hs | 55 ++ tests/UtilProperties.hs | 28 + 74 files changed, 7275 insertions(+) create mode 100644 .github/workflows/haskell.yml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100755 Setup.hs create mode 100644 bench/AES256GCMBench.hs create mode 100644 bench/AuthBench.hs create mode 100644 bench/BenchUtils.hs create mode 100644 bench/BoxBench.hs create mode 100644 bench/ChaCha20Poly1305Bench.hs create mode 100644 bench/ChaCha20Poly1305IETFBench.hs create mode 100644 bench/ConstantTimeBench.hs create mode 100644 bench/HashBench.hs create mode 100644 bench/Main.hs create mode 100644 bench/OneTimeAuthBench.hs create mode 100644 bench/PasswordBench.hs create mode 100644 bench/RandomBench.hs create mode 100644 bench/ScalarMultBench.hs create mode 100644 bench/SecretBoxBench.hs create mode 100644 bench/SignBench.hs create mode 100644 bench/StreamBench.hs create mode 100644 bench/XChaCha20Poly1305Bench.hs create mode 100644 saltine.cabal create mode 100644 src/Crypto/Saltine.hs create mode 100644 src/Crypto/Saltine/Class.hs create mode 100644 src/Crypto/Saltine/Core/AEAD.hs create mode 100644 src/Crypto/Saltine/Core/AEAD/AES256GCM.hs create mode 100644 src/Crypto/Saltine/Core/AEAD/ChaCha20Poly1305.hs create mode 100644 src/Crypto/Saltine/Core/AEAD/ChaCha20Poly1305IETF.hs create mode 100644 src/Crypto/Saltine/Core/AEAD/XChaCha20Poly1305.hs create mode 100644 src/Crypto/Saltine/Core/Auth.hs create mode 100644 src/Crypto/Saltine/Core/Box.hs create mode 100644 src/Crypto/Saltine/Core/Hash.hs create mode 100644 src/Crypto/Saltine/Core/OneTimeAuth.hs create mode 100644 src/Crypto/Saltine/Core/Password.hs create mode 100644 src/Crypto/Saltine/Core/ScalarMult.hs create mode 100644 src/Crypto/Saltine/Core/SecretBox.hs create mode 100644 src/Crypto/Saltine/Core/Sign.hs create mode 100644 src/Crypto/Saltine/Core/Stream.hs create mode 100644 src/Crypto/Saltine/Core/Utils.hs create mode 100644 src/Crypto/Saltine/Internal/AEAD/AES256GCM.hs create mode 100644 src/Crypto/Saltine/Internal/AEAD/ChaCha20Poly1305.hs create mode 100644 src/Crypto/Saltine/Internal/AEAD/ChaCha20Poly1305IETF.hs create mode 100644 src/Crypto/Saltine/Internal/AEAD/XChaCha20Poly1305.hs create mode 100644 src/Crypto/Saltine/Internal/Auth.hs create mode 100644 src/Crypto/Saltine/Internal/Box.hs create mode 100644 src/Crypto/Saltine/Internal/ByteSizes.hs create mode 100644 src/Crypto/Saltine/Internal/Hash.hs create mode 100644 src/Crypto/Saltine/Internal/OneTimeAuth.hs create mode 100644 src/Crypto/Saltine/Internal/Password.hs create mode 100644 src/Crypto/Saltine/Internal/ScalarMult.hs create mode 100644 src/Crypto/Saltine/Internal/SecretBox.hs create mode 100644 src/Crypto/Saltine/Internal/Sign.hs create mode 100644 src/Crypto/Saltine/Internal/Stream.hs create mode 100644 src/Crypto/Saltine/Internal/Util.hs create mode 100644 tests/AEAD/AES256GCMProperties.hs create mode 100644 tests/AEAD/ChaCha20Poly1305IETFProperties.hs create mode 100644 tests/AEAD/ChaCha20Poly1305Properties.hs create mode 100644 tests/AEAD/XChaCha20Poly1305Properties.hs create mode 100644 tests/AuthProperties.hs create mode 100644 tests/BoxProperties.hs create mode 100644 tests/HashProperties.hs create mode 100644 tests/Main.hs create mode 100644 tests/OneTimeAuthProperties.hs create mode 100644 tests/PasswordProperties.hs create mode 100644 tests/ScalarMultProperties.hs create mode 100644 tests/SealedBoxProperties.hs create mode 100644 tests/SecretBoxProperties.hs create mode 100644 tests/SignProperties.hs create mode 100644 tests/StreamProperties.hs create mode 100644 tests/Util.hs create mode 100644 tests/UtilProperties.hs diff --git a/.github/workflows/haskell.yml b/.github/workflows/haskell.yml new file mode 100644 index 00000000..2e5e4f3f --- /dev/null +++ b/.github/workflows/haskell.yml @@ -0,0 +1,53 @@ +name: Haskell CI + +on: [push, pull_request] + +jobs: + build: + if: "!contains(github.event.head_commit.message, 'skip ci') && !contains(github.event.head_commit.message, 'ci skip')" + runs-on: ubuntu-22.04 + + strategy: + matrix: + ghc-version: ['8.0.2','8.2.2','8.4.4','8.6.5','8.8.4','8.10.7','9.0.2', '9.2.6', '9.4.4'] + fail-fast: false + steps: + - uses: actions/checkout@v3 + + - name: Workaround runner image issue + if: ${{ runner.os == 'Linux' }} + # https://github.com/actions/runner-images/issues/7061 + run: sudo chown -R $USER /usr/local/.ghcup + + - uses: haskell/actions/setup@v2 + with: + ghc-version: ${{ matrix.ghc-version }} + cabal-version: '3.8' + + - name: Cache + uses: actions/cache@v2 + env: + cache-name: cache-cabal-ghc-${{ matrix.ghc-version }} + with: + path: ~/.cabal + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/*.cabal') }}-${{ hashFiles('**/cabal.project') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + + - name: Install libsodium + run: | + curl -# -L https://github.com/jedisct1/libsodium/releases/download/$SODIUMVER-RELEASE/libsodium-$SODIUMVER.tar.gz | tar xzf - + (cd libsodium-$SODIUMVER && ./autogen.sh && ./configure && make check && sudo make install && sudo ldconfig) + env: + SODIUMVER: 1.0.18 + + - name: Install dependencies + run: | + cabal update + cabal build --only-dependencies --enable-tests --enable-benchmarks + - name: Build + run: cabal build --enable-tests --enable-benchmarks all + - name: Run tests + run: cabal test all + - name: Install + run: cabal install --lib diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e61fbb17 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.hsenv* +dist* \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..125a5d77 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,44 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) + +## [Unreleased] +### Added +### Changed + +## [0.2.1.0] - 2023-02-17 +### Changed +- Fix Show instances formatting, and add instances for Keypairs, thanks [@NicolasT](https://github.com/NicolasT) + +## [0.2.0.1] - 2022-04-30 +### Changed +- Relax version bounds on text and bytestring, thanks [@ysangkok](https://github.com/ysangkok) + +## [0.2.0.0] - 2021-05-27 +### Added +- All AEAD variants are now in saltine +- Key comparisons now use sodium_memcmp to prevent timing attacks +- Liberal use of Internal modules +- Benchmarks added +- Export Key/Nonce/… constructors from Internal module +- New password hashing module +- Show instances for most (all?) relevant data types +- Signature types for detached functions + +### Changed +- newtype accessor functions added, keypairs are separate data types now instead +of tuples + +## [0.1.1.1] - 2021-01-15 +### Changed +- Fix for running tests in `cabal repl` (thanks [@timds]) +- Allow newer profunctors + +## [0.1.1.0] - 2020-02-29 +### Added +- bindings to generichash (Blake2), thanks [@donatello](https://github.com/donatello) + +### Changed +- Don't use `fail` in tests to fix compilation with GHC 8.8 +- Windows install instructions added, thanks [@tmcl](https://github.com/tmcl) diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..8eb902de --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Joseph Abrahamson + +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. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..2cdd2fac --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ + +deps: + cabal install --only-dependencies --enable-test -j4 + +configure: deps + cabal configure --enable-test + +build: configure + cabal build + +test: build + cabal test + +clean: + cabal clean + +all: test + + +.PHONY: deps configure build test all diff --git a/README.md b/README.md new file mode 100644 index 00000000..cb87b549 --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# Saltine 0.2.1.0 [![Hackage version](https://img.shields.io/hackage/v/saltine.svg?colorB=4FB900)](https://hackage.haskell.org/package/saltine) + +A Haskell binding for @jedisct1's portable binding for djb's +NaCl. **This is an early release.** Please try it out, but don't just +yet stake your life or job on it. + +It is imperative you call `sodiumInit` before using any other function. + +``` haskell +import Crypto.Saltine +import Crypto.Saltine.Core.SecretBox +import qualified Data.ByteString.Char8 as BSC8 + +main = do + sodiumInit + k <- newKey + n <- newNonce + let ciphertext = secretbox k n (BSC8.pack "foobar") + print $ secretboxOpen k n ciphertext + +-- Just "foobar" +``` + +In +[*The Security Impact of a New Cryptographic Library*](http://cryptojedi.org/papers/coolnacl-20111201.pdf) +Bernstein, Lange, and Schwabe argue that high-level cryptographic +libraries eliminate whole spaces of cryptographic disasters which are +nigh inevitable whenever programmers use low-level crypto primitives. + +* [Security Stack Exchange: Why Shouldn't We Roll Our Own?](http://security.stackexchange.com/questions/18197/why-shouldnt-we-roll-our-own) +* [Hacker News on "All the Crypto Code You've Ever Written is Probably Broken"](https://news.ycombinator.com/item?id=4779015) +* [Stack Overflow: When can you trust yourself to implement cryptography based solutions?](http://stackoverflow.com/questions/1914257/when-can-you-trust-yourself-to-implement-cryptography-based-solutions) +* [Coding Horror: Why isn't my encryption... encrypting?](http://www.codinghorror.com/blog/2009/05/why-isnt-my-encryption-encrypting.html) + +Crypto is complicated, so pre-rolled solutions are important +prevention mechanisms. + +[NaCl](http://nacl.cr.yp.to/) is Bernstein, Lange, and Schwabe's +solution: a high-level, performant cryptography library with a no-fuss +interface. [Saltine](http://github.com/tel/saltine) is a Haskell +binding to NaCl (via +[`libsodium`](https://github.com/jedisct1/libsodium)) which hopes to +provide even more simplicity and safety to the usage of cryptography. + +Note that it's still possible to shoot yourself in the foot pretty +easily using Saltine. Nonces must always be unique which must be managed +by the library user. +[`Crypto.Saltine.Core.Stream`](https://github.com/tel/saltine/blob/master/src/Crypto/Saltine/Core/Stream.hs) +produces messages which can beundetectably tampered with in-flight. +Keys are insecurely read from disk—they may be copied and then paged +back to disk. + +When uncertain, use [`Crypto.Saltine.Core.SecretBox`](https://github.com/tel/saltine/blob/master/src/Crypto/Saltine/Core/SecretBox.hs) +and [`Crypto.Saltine.Core.Box`](https://github.com/tel/saltine/blob/master/src/Crypto/Saltine/Core/Box.hs). +If you can think of ways to use Haskell's type system to enforce +security invariants, please suggest them. + +To use it on Windows systems, download +[a prebuild libsodium-\*-stable-mingw.tar.gz file](https://download.libsodium.org/libsodium/releases/) +and copy the files in `libsodium-win64` into the equivalent places +in `C:\Program Files\Haskell Platform\*\mingw`. Then just add saltine +to your cabal file and watch it go. + +Tested with [`libsodium-1.0.18`](https://download.libsodium.org/libsodium/releases/). + +Inspired by @thoughtpolice's +[`salt`](http://github.com/thoughtpolice/salt) library. `salt` also +binds to NaCl, but uses a Haskell managed version of djb's code +instead of `libsodium`. diff --git a/Setup.hs b/Setup.hs new file mode 100755 index 00000000..a6a22c8e --- /dev/null +++ b/Setup.hs @@ -0,0 +1,3 @@ +#!/usr/bin/env runhaskell +import Distribution.Simple +main = defaultMain \ No newline at end of file diff --git a/bench/AES256GCMBench.hs b/bench/AES256GCMBench.hs new file mode 100644 index 00000000..11f65d89 --- /dev/null +++ b/bench/AES256GCMBench.hs @@ -0,0 +1,68 @@ +module AES256GCMBench (benchAes256GCM, aes256GCMEnv) where + +import Criterion.Main + +import Control.Monad +import Control.DeepSeq +import Control.Exception +import Data.ByteString as BS + +import Crypto.Saltine.Core.AEAD.AES256GCM as G + +import BenchUtils + +aes256GCMEnv :: IO Key +aes256GCMEnv = newKey + +benchAes256GCM :: Key -> Benchmark +benchAes256GCM k = do + let encrypt :: ByteString -> ByteString -> IO ByteString + encrypt msg aad = newNonce >>= \n -> pure $ G.aead k n msg aad + + decrypt :: ByteString -> ByteString -> IO (Maybe ByteString) + decrypt msg aad = do + n <- newNonce + let ciphertext = G.aead k n msg aad + return $ G.aeadOpen k n ciphertext aad + + encryptDetached msg aad = newNonce >>= \n -> pure $ G.aeadDetached k n msg aad + decryptDetached msg aad = do + n <- newNonce + let (t,c) = G.aeadDetached k n msg aad + pure $ G.aeadOpenDetached k n t c aad + + bgroup "AES256GCM" + [ bench "newKey" $ nfIO newKey + , bgroup "aead" + [ bench "128 B + 128 B" $ nfIO $ encrypt bs128 bs128 + , bench "128 B + 5 MB" $ nfIO $ encrypt bs128 mb5 + , bench "1 MB + 128 B" $ nfIO $ encrypt mb1 bs128 + , bench "1 MB + 5 B" $ nfIO $ encrypt mb1 mb5 + , bench "5 MB + 128 B" $ nfIO $ encrypt mb5 bs128 + , bench "5 MB + 5 MB" $ nfIO $ encrypt mb5 mb5 + ] + , bgroup "aead + open" + [ bench "128 B + 128 B" $ nfIO $ decrypt bs128 bs128 + , bench "128 B + 5 MB" $ nfIO $ decrypt bs128 mb5 + , bench "1 MB + 128 B" $ nfIO $ decrypt mb1 bs128 + , bench "1 MB + 5 B" $ nfIO $ decrypt mb1 mb5 + , bench "5 MB + 128 B" $ nfIO $ decrypt mb5 bs128 + , bench "5 MB + 5 MB" $ nfIO $ decrypt mb5 mb5 + ] + , bgroup "aeadDetached" + [ bench "128 B + 128 B" $ nfIO $ encryptDetached bs128 bs128 + , bench "128 B + 5 MB" $ nfIO $ encryptDetached bs128 mb5 + , bench "1 MB + 128 B" $ nfIO $ encryptDetached mb1 bs128 + , bench "1 MB + 5 B" $ nfIO $ encryptDetached mb1 mb5 + , bench "5 MB + 128 B" $ nfIO $ encryptDetached mb5 bs128 + , bench "5 MB + 5 MB" $ nfIO $ encryptDetached mb5 mb5 + ] + , bgroup "aeadDetached + openDetached" + [ bench "128 B + 128 B" $ nfIO $ decryptDetached bs128 bs128 + , bench "128 B + 5 MB" $ nfIO $ decryptDetached bs128 mb5 + , bench "1 MB + 128 B" $ nfIO $ decryptDetached mb1 bs128 + , bench "1 MB + 5 B" $ nfIO $ decryptDetached mb1 mb5 + , bench "5 MB + 128 B" $ nfIO $ decryptDetached mb5 bs128 + , bench "5 MB + 5 MB" $ nfIO $ decryptDetached mb5 mb5 + ] + ] diff --git a/bench/AuthBench.hs b/bench/AuthBench.hs new file mode 100644 index 00000000..8550a382 --- /dev/null +++ b/bench/AuthBench.hs @@ -0,0 +1,37 @@ +module AuthBench (benchAuth, authEnv) where + +import Criterion.Main + +import Control.Monad +import Control.DeepSeq +import Control.Exception +import Data.ByteString as BS + +import Crypto.Saltine.Core.Auth + +import BenchUtils + + +authEnv :: IO Key +authEnv = newKey + +benchAuth :: Key -> Benchmark +benchAuth k = do + let authVerify :: ByteString -> Bool + authVerify message = do + let authenticator = auth k message + verify k authenticator message + + bgroup "Auth" + [ bench "newKey" $ nfIO newKey + , bgroup "auth" + [ bench "128 B" $ nf (auth k) bs128 + , bench "1 MB" $ nf (auth k) mb1 + , bench "5 MB" $ nf (auth k) mb5 + ] + , bgroup "auth+verify" + [ bench "128 B" $ nf authVerify bs128 + , bench "1 MB" $ nf authVerify mb1 + , bench "5 MB" $ nf authVerify mb5 + ] + ] diff --git a/bench/BenchUtils.hs b/bench/BenchUtils.hs new file mode 100644 index 00000000..cf6360db --- /dev/null +++ b/bench/BenchUtils.hs @@ -0,0 +1,15 @@ +module BenchUtils where + +import Data.ByteString (ByteString) +import qualified Data.ByteString as BS + +import Data.Text as T + +bs128, kb2, mb1, mb5 :: ByteString +bs128 = BS.replicate 128 0 +kb2 = BS.replicate 2000 0 +mb1 = BS.replicate 1000000 0 +mb5 = BS.replicate 5000000 0 + +s128 = T.replicate 128 (T.pack "0") +s2000 = T.replicate 2000 (T.pack "0") diff --git a/bench/BoxBench.hs b/bench/BoxBench.hs new file mode 100644 index 00000000..27b61b85 --- /dev/null +++ b/bench/BoxBench.hs @@ -0,0 +1,45 @@ +module BoxBench (benchBox, boxEnv) where + +import Criterion.Main + +import Control.Monad +import Control.DeepSeq +import Control.Exception +import Data.ByteString as BS + +import Crypto.Saltine.Core.Box + +import BenchUtils + + +boxEnv :: IO (Keypair, Keypair) +boxEnv = do + alice <- newKeypair + bob <- newKeypair + return (alice, bob) + + +benchBox :: (Keypair, Keypair) -> Benchmark +benchBox (alice, bob) = do + let encrypt :: ByteString -> IO ByteString + encrypt b = newNonce >>= \n -> pure $ box (publicKey bob) (secretKey alice) n b + + decrypt :: ByteString -> IO (Maybe ByteString) + decrypt message = do + n <- newNonce + let ciphertext = box (publicKey alice) (secretKey bob) n message + return $ boxOpen (publicKey bob) (secretKey alice) n ciphertext + + bgroup "Box" + [ bench "newKeypair" $ nfIO newKeypair + , bgroup "encrypt" + [ bench "128 B" $ nfIO $ encrypt bs128 + , bench "1 MB" $ nfIO $ encrypt mb1 + , bench "5 MB" $ nfIO $ encrypt mb5 + ] + , bgroup "encrypt+decrypt" + [ bench "128 B" $ nfIO $ decrypt bs128 + , bench "1 MB" $ nfIO $ decrypt mb1 + , bench "5 MB" $ nfIO $ decrypt mb5 + ] + ] diff --git a/bench/ChaCha20Poly1305Bench.hs b/bench/ChaCha20Poly1305Bench.hs new file mode 100644 index 00000000..a48eabfa --- /dev/null +++ b/bench/ChaCha20Poly1305Bench.hs @@ -0,0 +1,68 @@ +module ChaCha20Poly1305Bench (benchChaCha20Poly1305, chaCha20Poly1305Env) where + +import Criterion.Main + +import Control.Monad +import Control.DeepSeq +import Control.Exception +import Data.ByteString as BS + +import Crypto.Saltine.Core.AEAD.ChaCha20Poly1305 as C + +import BenchUtils + +chaCha20Poly1305Env :: IO Key +chaCha20Poly1305Env = newKey + +benchChaCha20Poly1305 :: Key -> Benchmark +benchChaCha20Poly1305 k = do + let encrypt :: ByteString -> ByteString -> IO ByteString + encrypt msg aad = newNonce >>= \n -> pure $ C.aead k n msg aad + + decrypt :: ByteString -> ByteString -> IO (Maybe ByteString) + decrypt msg aad = do + n <- newNonce + let ciphertext = C.aead k n msg aad + return $ C.aeadOpen k n ciphertext aad + + encryptDetached msg aad = newNonce >>= \n -> pure $ C.aeadDetached k n msg aad + decryptDetached msg aad = do + n <- newNonce + let (t,c) = C.aeadDetached k n msg aad + pure $ C.aeadOpenDetached k n t c aad + + bgroup "ChaCha20Poly1305" + [ bench "newKey" $ nfIO newKey + , bgroup "aead" + [ bench "128 B + 128 B" $ nfIO $ encrypt bs128 bs128 + , bench "128 B + 5 MB" $ nfIO $ encrypt bs128 mb5 + , bench "1 MB + 128 B" $ nfIO $ encrypt mb1 bs128 + , bench "1 MB + 5 B" $ nfIO $ encrypt mb1 mb5 + , bench "5 MB + 128 B" $ nfIO $ encrypt mb5 bs128 + , bench "5 MB + 5 MB" $ nfIO $ encrypt mb5 mb5 + ] + , bgroup "aead + open" + [ bench "128 B + 128 B" $ nfIO $ decrypt bs128 bs128 + , bench "128 B + 5 MB" $ nfIO $ decrypt bs128 mb5 + , bench "1 MB + 128 B" $ nfIO $ decrypt mb1 bs128 + , bench "1 MB + 5 B" $ nfIO $ decrypt mb1 mb5 + , bench "5 MB + 128 B" $ nfIO $ decrypt mb5 bs128 + , bench "5 MB + 5 MB" $ nfIO $ decrypt mb5 mb5 + ] + , bgroup "aeadDetached" + [ bench "128 B + 128 B" $ nfIO $ encryptDetached bs128 bs128 + , bench "128 B + 5 MB" $ nfIO $ encryptDetached bs128 mb5 + , bench "1 MB + 128 B" $ nfIO $ encryptDetached mb1 bs128 + , bench "1 MB + 5 B" $ nfIO $ encryptDetached mb1 mb5 + , bench "5 MB + 128 B" $ nfIO $ encryptDetached mb5 bs128 + , bench "5 MB + 5 MB" $ nfIO $ encryptDetached mb5 mb5 + ] + , bgroup "aeadDetached + openDetached" + [ bench "128 B + 128 B" $ nfIO $ decryptDetached bs128 bs128 + , bench "128 B + 5 MB" $ nfIO $ decryptDetached bs128 mb5 + , bench "1 MB + 128 B" $ nfIO $ decryptDetached mb1 bs128 + , bench "1 MB + 5 B" $ nfIO $ decryptDetached mb1 mb5 + , bench "5 MB + 128 B" $ nfIO $ decryptDetached mb5 bs128 + , bench "5 MB + 5 MB" $ nfIO $ decryptDetached mb5 mb5 + ] + ] diff --git a/bench/ChaCha20Poly1305IETFBench.hs b/bench/ChaCha20Poly1305IETFBench.hs new file mode 100644 index 00000000..918e0e1e --- /dev/null +++ b/bench/ChaCha20Poly1305IETFBench.hs @@ -0,0 +1,68 @@ +module ChaCha20Poly1305IETFBench (benchChaCha20Poly1305IETF, chaCha20Poly1305IETFEnv) where + +import Criterion.Main + +import Control.Monad +import Control.DeepSeq +import Control.Exception +import Data.ByteString as BS + +import Crypto.Saltine.Core.AEAD.ChaCha20Poly1305IETF as C + +import BenchUtils + +chaCha20Poly1305IETFEnv :: IO Key +chaCha20Poly1305IETFEnv = newKey + +benchChaCha20Poly1305IETF :: Key -> Benchmark +benchChaCha20Poly1305IETF k = do + let encrypt :: ByteString -> ByteString -> IO ByteString + encrypt msg aad = newNonce >>= \n -> pure $ C.aead k n msg aad + + decrypt :: ByteString -> ByteString -> IO (Maybe ByteString) + decrypt msg aad = do + n <- newNonce + let ciphertext = C.aead k n msg aad + return $ C.aeadOpen k n ciphertext aad + + encryptDetached msg aad = newNonce >>= \n -> pure $ C.aeadDetached k n msg aad + decryptDetached msg aad = do + n <- newNonce + let (t,c) = C.aeadDetached k n msg aad + pure $ C.aeadOpenDetached k n t c aad + + bgroup "ChaCha20Poly1305IETF" + [ bench "newKey" $ nfIO newKey + , bgroup "aead" + [ bench "128 B + 128 B" $ nfIO $ encrypt bs128 bs128 + , bench "128 B + 5 MB" $ nfIO $ encrypt bs128 mb5 + , bench "1 MB + 128 B" $ nfIO $ encrypt mb1 bs128 + , bench "1 MB + 5 B" $ nfIO $ encrypt mb1 mb5 + , bench "5 MB + 128 B" $ nfIO $ encrypt mb5 bs128 + , bench "5 MB + 5 MB" $ nfIO $ encrypt mb5 mb5 + ] + , bgroup "aead + open" + [ bench "128 B + 128 B" $ nfIO $ decrypt bs128 bs128 + , bench "128 B + 5 MB" $ nfIO $ decrypt bs128 mb5 + , bench "1 MB + 128 B" $ nfIO $ decrypt mb1 bs128 + , bench "1 MB + 5 B" $ nfIO $ decrypt mb1 mb5 + , bench "5 MB + 128 B" $ nfIO $ decrypt mb5 bs128 + , bench "5 MB + 5 MB" $ nfIO $ decrypt mb5 mb5 + ] + , bgroup "aeadDetached" + [ bench "128 B + 128 B" $ nfIO $ encryptDetached bs128 bs128 + , bench "128 B + 5 MB" $ nfIO $ encryptDetached bs128 mb5 + , bench "1 MB + 128 B" $ nfIO $ encryptDetached mb1 bs128 + , bench "1 MB + 5 B" $ nfIO $ encryptDetached mb1 mb5 + , bench "5 MB + 128 B" $ nfIO $ encryptDetached mb5 bs128 + , bench "5 MB + 5 MB" $ nfIO $ encryptDetached mb5 mb5 + ] + , bgroup "aeadDetached + openDetached" + [ bench "128 B + 128 B" $ nfIO $ decryptDetached bs128 bs128 + , bench "128 B + 5 MB" $ nfIO $ decryptDetached bs128 mb5 + , bench "1 MB + 128 B" $ nfIO $ decryptDetached mb1 bs128 + , bench "1 MB + 5 B" $ nfIO $ decryptDetached mb1 mb5 + , bench "5 MB + 128 B" $ nfIO $ decryptDetached mb5 bs128 + , bench "5 MB + 5 MB" $ nfIO $ decryptDetached mb5 mb5 + ] + ] diff --git a/bench/ConstantTimeBench.hs b/bench/ConstantTimeBench.hs new file mode 100644 index 00000000..d604f68e --- /dev/null +++ b/bench/ConstantTimeBench.hs @@ -0,0 +1,29 @@ +module ConstantTimeBench (benchComparison) where + +import Criterion.Main + +import Control.Monad +import Control.DeepSeq +import Control.Exception +import Data.ByteString as BS + +import Crypto.Saltine.Core.Auth as A +import Crypto.Saltine.Class +import Crypto.Saltine.Internal.Util as U + +import BenchUtils + +benchComparison :: Benchmark +benchComparison = + bgroup "ConstantTime" + [ bench "Compare two \"keys\" using ByteString comparison" $ nfIO $ do + k1 <- randomByteString (2^20) + k2 <- randomByteString (2^20) + + pure $ k1 == k2 + , bench "Compare two keys using constant-time comparison" $ nfIO $ do + k1 <- randomByteString (2^20) + k2 <- randomByteString (2^20) + + pure $ U.compare k1 k2 + ] diff --git a/bench/HashBench.hs b/bench/HashBench.hs new file mode 100644 index 00000000..3b7401d9 --- /dev/null +++ b/bench/HashBench.hs @@ -0,0 +1,40 @@ +module HashBench (benchHash, hashEnv) where + +import Criterion + +import Control.Monad + +import Crypto.Saltine.Core.Hash +import Crypto.Saltine.Core.Utils + +import BenchUtils +import Data.Maybe (fromJust) + +hashEnv :: IO (ShorthashKey, GenerichashKey, GenerichashOutLen) +hashEnv = do + shk <- newShorthashKey + ghk <- fromJust <$> newGenerichashKey 48 + let ghol = fromJust (generichashOutLen 48) + + pure (shk,ghk,ghol) + +benchHash :: (ShorthashKey, GenerichashKey, GenerichashOutLen) -> Benchmark +benchHash (shk,ghk,ghol) = + bgroup "Hash" + [ bgroup "hash" + [ bench "128 B" $ nf hash bs128 + , bench "1 MB" $ nf hash mb1 + , bench "5 MB" $ nf hash mb5 + ] + , bgroup "shortHash" + [ bench "128 B" $ nf (shorthash shk) bs128 + , bench "2 KB" $ nf (shorthash shk) kb2 + , bench "1 MB" $ nf (shorthash shk) mb1 + ] + , bgroup "genericHash" + [ bench "128 B" $ nf (generichash ghk bs128) ghol + , bench "2 KB" $ nf (generichash ghk kb2 ) ghol + , bench "1 MB" $ nf (generichash ghk mb1 ) ghol + , bench "5 MB" $ nf (generichash ghk mb5 ) ghol + ] + ] diff --git a/bench/Main.hs b/bench/Main.hs new file mode 100644 index 00000000..4dd9321b --- /dev/null +++ b/bench/Main.hs @@ -0,0 +1,79 @@ +import Criterion.Main + +import Control.Monad +import Control.DeepSeq +import Control.Exception + +import AuthBench +import OneTimeAuthBench +import BoxBench +import SecretBoxBench +import ConstantTimeBench +import HashBench +import RandomBench +import ScalarMultBench +import SignBench +import StreamBench +import PasswordBench +import AES256GCMBench +import ChaCha20Poly1305Bench +import ChaCha20Poly1305IETFBench +import XChaCha20Poly1305Bench + +main :: IO () +main = do + authKeyToEval <- authEnv + authKey <- evaluate $ force authKeyToEval + + oneTimeAuthKeyToEval <- oneTimeAuthEnv + oneTimeAuthKey <- evaluate $ force oneTimeAuthKeyToEval + + boxToEval <- boxEnv + boxKeys <- evaluate $ force boxToEval + + secretboxKeyToEval <- secretboxEnv + secretboxKey <- evaluate $ force secretboxKeyToEval + + scmlToEval <- scalarMultEnv + scml <- evaluate $ force scmlToEval + + signToEval <- signEnv + signKey <- evaluate $ force signToEval + + streamKeyToEval <- streamEnv + streamKey <- evaluate $ force streamKeyToEval + + passwordSaltToEval <- passwordEnv + passwordSalt <- evaluate $ force passwordSaltToEval + + hashKeysToEval <- hashEnv + hashKeys <- evaluate $ force hashKeysToEval + + aes256GCMKeyToEval <- aes256GCMEnv + aes256GCMKey <- evaluate $ force aes256GCMKeyToEval + + chaCha20Poly1305KeyToEval <- chaCha20Poly1305Env + chaCha20Poly1305Key <- evaluate $ force chaCha20Poly1305KeyToEval + + chaCha20Poly1305IETFKeyToEval <- chaCha20Poly1305IETFEnv + chaCha20Poly1305IETFKey <- evaluate $ force chaCha20Poly1305IETFKeyToEval + + xChaCha20Poly1305KeyToEval <- xChaCha20Poly1305Env + xChaCha20Poly1305Key <- evaluate $ force xChaCha20Poly1305KeyToEval + + defaultMain [ + benchAuth authKey + , benchOneTimeAuth oneTimeAuthKey + , benchBox boxKeys + , benchSecretbox secretboxKey + , benchHash hashKeys + , benchScalarMult scml + , benchSign signKey + , benchStream streamKey + , benchPassword passwordSalt + , benchComparison + , benchAes256GCM aes256GCMKey + , benchChaCha20Poly1305 chaCha20Poly1305Key + , benchChaCha20Poly1305IETF chaCha20Poly1305IETFKey + , benchXChaCha20Poly1305 xChaCha20Poly1305Key + ] diff --git a/bench/OneTimeAuthBench.hs b/bench/OneTimeAuthBench.hs new file mode 100644 index 00000000..0b04a000 --- /dev/null +++ b/bench/OneTimeAuthBench.hs @@ -0,0 +1,37 @@ +module OneTimeAuthBench (benchOneTimeAuth, oneTimeAuthEnv) where + +import Criterion.Main + +import Control.Monad +import Control.DeepSeq +import Control.Exception +import Data.ByteString as BS + +import Crypto.Saltine.Core.OneTimeAuth + +import BenchUtils + + +oneTimeAuthEnv :: IO Key +oneTimeAuthEnv = newKey + +benchOneTimeAuth :: Key -> Benchmark +benchOneTimeAuth k = do + let authVerify :: ByteString -> Bool + authVerify message = do + let authenticator = auth k message + verify k authenticator message + + bgroup "OneTimeAuth" + [ bench "newKey" $ nfIO newKey + , bgroup "auth" + [ bench "128 B" $ nf (auth k) bs128 + , bench "1 MB" $ nf (auth k) mb1 + , bench "5 MB" $ nf (auth k) mb5 + ] + , bgroup "auth+verify" + [ bench "128 B" $ nf authVerify bs128 + , bench "1 MB" $ nf authVerify mb1 + , bench "5 MB" $ nf authVerify mb5 + ] + ] diff --git a/bench/PasswordBench.hs b/bench/PasswordBench.hs new file mode 100644 index 00000000..e677bfeb --- /dev/null +++ b/bench/PasswordBench.hs @@ -0,0 +1,87 @@ +module PasswordBench (benchPassword, passwordEnv) where + +import Criterion.Main + +import Control.Monad +import Control.DeepSeq +import Control.Exception +import Data.ByteString as BS +import Data.Maybe (fromJust) +import Data.Text (Text) + +import Crypto.Saltine.Core.Password as P + +import BenchUtils + +passwordEnv :: IO Salt +passwordEnv = newSalt + +benchPassword :: Salt -> Benchmark +benchPassword s = do + let hashAndVerify :: Text -> Policy -> IO Bool + hashAndVerify p pol = do + h <- pwhashStr p pol + pure $ pwhashStrVerify (fromJust h) p + + hashAndRehash :: Text -> Policy -> IO (Maybe Bool) + hashAndRehash p pol = do + h <- pwhashStr p pol + pure $ needsRehash (opsPolicy pol) (memPolicy pol) (fromJust h) + + bgroup "Password" + [ bench "newSalt" $ nfIO newSalt + , bgroup "hash + verify" + [ bgroup "interactive" + [ bench "128 B" $ nfIO $ hashAndVerify s128 interactivePolicy + , bench "2 KB" $ nfIO $ hashAndVerify s2000 interactivePolicy + ] + , bgroup "moderate" + [ bench "128 B" $ nfIO $ hashAndVerify s128 moderatePolicy + , bench "2 KB" $ nfIO $ hashAndVerify s2000 moderatePolicy + ] + , bgroup "sensitive" + [ bench "128 B" $ nfIO $ hashAndVerify s128 sensitivePolicy + , bench "2 KB" $ nfIO $ hashAndVerify s2000 sensitivePolicy + ] + ] + , bgroup "needsRehash" + [ bgroup "interactive" + [ bench "128 B" $ nfIO $ hashAndRehash s128 interactivePolicy + , bench "2 KB" $ nfIO $ hashAndRehash s2000 interactivePolicy + ] + , bgroup "moderate" + [ bench "128 B" $ nfIO $ hashAndRehash s128 moderatePolicy + , bench "2 KB" $ nfIO $ hashAndRehash s2000 moderatePolicy + ] + , bgroup "sensitive" + [ bench "128 B" $ nfIO $ hashAndRehash s128 sensitivePolicy + , bench "2 KB" $ nfIO $ hashAndRehash s2000 sensitivePolicy + ] + ] + , bgroup "pwhash" + [ bgroup "interactive" + [ bench "128 B + 256" $ nf (pwhash s128 (2^8 ) s) interactivePolicy + , bench "128 B + 512" $ nf (pwhash s128 (2^9 ) s) interactivePolicy + , bench "128 B + 1024" $ nf (pwhash s128 (2^10) s) interactivePolicy + , bench "128 B + 2048" $ nf (pwhash s128 (2^11) s) interactivePolicy + , bench "128 B + 4096" $ nf (pwhash s128 (2^12) s) interactivePolicy + , bench "128 B + 8192" $ nf (pwhash s128 (2^13) s) interactivePolicy + ] + , bgroup "moderate" + [ bench "128 B + 256" $ nf (pwhash s128 (2^8 ) s) moderatePolicy + , bench "128 B + 512" $ nf (pwhash s128 (2^9 ) s) moderatePolicy + , bench "128 B + 1024" $ nf (pwhash s128 (2^10) s) moderatePolicy + , bench "128 B + 2048" $ nf (pwhash s128 (2^11) s) moderatePolicy + , bench "128 B + 4096" $ nf (pwhash s128 (2^12) s) moderatePolicy + , bench "128 B + 8192" $ nf (pwhash s128 (2^13) s) moderatePolicy + ] + , bgroup "sensitive" + [ bench "128 B + 256" $ nf (pwhash s128 (2^8 ) s) sensitivePolicy + , bench "128 B + 512" $ nf (pwhash s128 (2^9 ) s) sensitivePolicy + , bench "128 B + 1024" $ nf (pwhash s128 (2^10) s) sensitivePolicy + , bench "128 B + 2048" $ nf (pwhash s128 (2^11) s) sensitivePolicy + , bench "128 B + 4096" $ nf (pwhash s128 (2^12) s) sensitivePolicy + , bench "128 B + 8192" $ nf (pwhash s128 (2^13) s) sensitivePolicy + ] + ] + ] diff --git a/bench/RandomBench.hs b/bench/RandomBench.hs new file mode 100644 index 00000000..2bb16a2a --- /dev/null +++ b/bench/RandomBench.hs @@ -0,0 +1,21 @@ +module RandomBench (benchRandom) where + +import Criterion + +import Data.ByteString (ByteString) +import Crypto.Saltine.Core.Utils + +benchRandom = bgroup "random" + [ bench "32 B" $ nfIO (randomByteString 32 :: IO ByteString) + , bench "128 B" $ nfIO (randomByteString 128 :: IO ByteString) + , bench "512 B" $ nfIO (randomByteString 512 :: IO ByteString) + , bench "2 KB" $ nfIO (randomByteString 2000 :: IO ByteString) + , bench "8 KB" $ nfIO (randomByteString 8000 :: IO ByteString) + , bench "32 KB" $ nfIO (randomByteString 32000 :: IO ByteString) + , bench "128 KB" $ nfIO (randomByteString 128000 :: IO ByteString) + , bench "512 KB" $ nfIO (randomByteString 512000 :: IO ByteString) + , bench "2 MB" $ nfIO (randomByteString 2000000 :: IO ByteString) + , bench "8 MB" $ nfIO (randomByteString 8000000 :: IO ByteString) + , bench "32 MB" $ nfIO (randomByteString 32000000 :: IO ByteString) + , bench "128 MB" $ nfIO (randomByteString 128000000 :: IO ByteString) + ] diff --git a/bench/ScalarMultBench.hs b/bench/ScalarMultBench.hs new file mode 100644 index 00000000..5faeb066 --- /dev/null +++ b/bench/ScalarMultBench.hs @@ -0,0 +1,35 @@ +module ScalarMultBench (benchScalarMult, scalarMultEnv) where + +import Criterion.Main + +import Control.Monad +import Control.DeepSeq +import Control.Exception +import Data.ByteString as BS + +import Data.Maybe (fromJust) + +import Crypto.Saltine.Class +import Crypto.Saltine.Core.ScalarMult as S +import Crypto.Saltine.Internal.ScalarMult as Bytes +import Crypto.Saltine.Internal.Util + +import BenchUtils + +scalarMultEnv :: IO (GroupElement, Scalar) +scalarMultEnv = do + bsge <- randomByteString Bytes.scalarmult_bytes + bssc <- randomByteString Bytes.scalarmult_scalarbytes + + let ge = fromJust $ decode bsge + let sc = fromJust $ decode bssc + + pure (ge,sc) + + +benchScalarMult :: (GroupElement, Scalar) -> Benchmark +benchScalarMult (ge,sc) = + bgroup "ScalarMult" + [ bench "mult" $ nf (S.mult sc) ge + , bench "multBase" $ nf S.multBase sc + ] diff --git a/bench/SecretBoxBench.hs b/bench/SecretBoxBench.hs new file mode 100644 index 00000000..bef2652d --- /dev/null +++ b/bench/SecretBoxBench.hs @@ -0,0 +1,57 @@ +module SecretBoxBench (benchSecretbox, secretboxEnv) where + +import Criterion.Main + +import Control.Monad +import Control.DeepSeq +import Control.Exception +import Data.ByteString as BS + +import Crypto.Saltine.Core.SecretBox + +import BenchUtils + + +secretboxEnv :: IO Key +secretboxEnv = newKey + +benchSecretbox :: Key -> Benchmark +benchSecretbox k = do + let encrypt :: ByteString -> IO ByteString + encrypt msg = newNonce >>= \n -> pure $ secretbox k n msg + + decrypt :: ByteString -> IO (Maybe ByteString) + decrypt msg = do + n <- newNonce + let ciphertext = secretbox k n msg + return $ secretboxOpen k n ciphertext + + encryptDetached msg = newNonce >>= \n -> pure $ secretboxDetached k n msg + decryptDetached msg = do + n <- newNonce + let (t,c) = secretboxDetached k n msg + pure $ secretboxOpenDetached k n t c + + bgroup "Box" + [ bench "newKey" $ nfIO newKey + , bgroup "encrypt" + [ bench "128 B" $ nfIO $ encrypt bs128 + , bench "1 MB" $ nfIO $ encrypt mb1 + , bench "5 MB" $ nfIO $ encrypt mb5 + ] + , bgroup "encrypt+decrypt" + [ bench "128 B" $ nfIO $ decrypt bs128 + , bench "1 MB" $ nfIO $ decrypt mb1 + , bench "5 MB" $ nfIO $ decrypt mb5 + ] + , bgroup "encryptDetached" + [ bench "128 B" $ nfIO $ encryptDetached bs128 + , bench "1 MB" $ nfIO $ encryptDetached mb1 + , bench "5 MB" $ nfIO $ encryptDetached mb5 + ] + , bgroup "encryptDetached+decryptDetached" + [ bench "128 B" $ nfIO $ decryptDetached bs128 + , bench "1 MB" $ nfIO $ decryptDetached mb1 + , bench "5 MB" $ nfIO $ decryptDetached mb5 + ] + ] diff --git a/bench/SignBench.hs b/bench/SignBench.hs new file mode 100644 index 00000000..9f8d7963 --- /dev/null +++ b/bench/SignBench.hs @@ -0,0 +1,53 @@ +module SignBench (benchSign, signEnv) where + +import Criterion.Main + +import Control.Monad +import Control.DeepSeq +import Control.Exception +import Data.ByteString as BS + +import Crypto.Saltine.Core.Sign as S + +import BenchUtils + +signEnv :: IO Keypair +signEnv = newKeypair + +benchSign :: Keypair -> Benchmark +benchSign alice = do + let sign :: ByteString -> ByteString + sign = S.sign (secretKey alice) + + verify :: ByteString -> Bool + verify message = + let signed = sign message + in case S.signOpen (publicKey alice) signed of + Nothing -> False + Just ms -> True + + signDetached = S.signDetached (secretKey alice) + signVerifyDetached message = S.signVerifyDetached (publicKey alice) (signDetached message) + bgroup "Sign" + [ bench "newKeypair" $ nfIO newKeypair + , bgroup "sign" + [ bench "128 B" $ nf sign bs128 + , bench "1 MB" $ nf sign mb1 + , bench "5 MB" $ nf sign mb5 + ] + , bgroup "sign+verify" + [ bench "128 B" $ nf verify bs128 + , bench "1 MB" $ nf verify mb1 + , bench "5 MB" $ nf verify mb5 + ] + , bgroup "signDetached" + [ bench "128 B" $ nf signDetached bs128 + , bench "1 MB" $ nf signDetached mb1 + , bench "5 MB" $ nf signDetached mb5 + ] + , bgroup "signDetached+verifyDetached" + [ bench "128 B" $ nf signVerifyDetached bs128 + , bench "1 MB" $ nf signVerifyDetached mb1 + , bench "5 MB" $ nf signVerifyDetached mb5 + ] + ] diff --git a/bench/StreamBench.hs b/bench/StreamBench.hs new file mode 100644 index 00000000..c50ac0d6 --- /dev/null +++ b/bench/StreamBench.hs @@ -0,0 +1,37 @@ +module StreamBench (benchStream, streamEnv) where + +import Criterion.Main + +import Control.Monad +import Control.DeepSeq +import Control.Exception +import Data.ByteString as BS + +import Crypto.Saltine.Core.Stream as S + +import BenchUtils + +streamEnv :: IO Key +streamEnv = newKey + +benchStream :: Key -> Benchmark +benchStream k = do + let stream :: Int -> IO ByteString + stream i = newNonce >>= \n -> pure $ S.stream k n i + + xor :: ByteString -> IO ByteString + xor m = newNonce >>= \n -> pure $ S.xor k n m + + bgroup "Stream" + [ bench "newKey" $ nfIO newKey + , bgroup "stream" + [ bench "128 B" $ nfIO $ stream (2^7) + , bench "1 MB" $ nfIO $ stream (2^20) + , bench "16 MB" $ nfIO $ stream (2^24) + ] + , bgroup "xor" + [ bench "128 B" $ nfIO $ xor bs128 + , bench "1 MB" $ nfIO $ xor mb1 + , bench "5 MB" $ nfIO $ xor mb5 + ] + ] diff --git a/bench/XChaCha20Poly1305Bench.hs b/bench/XChaCha20Poly1305Bench.hs new file mode 100644 index 00000000..aa767187 --- /dev/null +++ b/bench/XChaCha20Poly1305Bench.hs @@ -0,0 +1,68 @@ +module XChaCha20Poly1305Bench (benchXChaCha20Poly1305, xChaCha20Poly1305Env) where + +import Criterion.Main + +import Control.Monad +import Control.DeepSeq +import Control.Exception +import Data.ByteString as BS + +import Crypto.Saltine.Core.AEAD.XChaCha20Poly1305 as C + +import BenchUtils + +xChaCha20Poly1305Env :: IO Key +xChaCha20Poly1305Env = newKey + +benchXChaCha20Poly1305 :: Key -> Benchmark +benchXChaCha20Poly1305 k = do + let encrypt :: ByteString -> ByteString -> IO ByteString + encrypt msg aad = newNonce >>= \n -> pure $ C.aead k n msg aad + + decrypt :: ByteString -> ByteString -> IO (Maybe ByteString) + decrypt msg aad = do + n <- newNonce + let ciphertext = C.aead k n msg aad + return $ C.aeadOpen k n ciphertext aad + + encryptDetached msg aad = newNonce >>= \n -> pure $ C.aeadDetached k n msg aad + decryptDetached msg aad = do + n <- newNonce + let (t,c) = C.aeadDetached k n msg aad + pure $ C.aeadOpenDetached k n t c aad + + bgroup "XChaCha20Poly1305" + [ bench "newKey" $ nfIO newKey + , bgroup "aead" + [ bench "128 B + 128 B" $ nfIO $ encrypt bs128 bs128 + , bench "128 B + 5 MB" $ nfIO $ encrypt bs128 mb5 + , bench "1 MB + 128 B" $ nfIO $ encrypt mb1 bs128 + , bench "1 MB + 5 B" $ nfIO $ encrypt mb1 mb5 + , bench "5 MB + 128 B" $ nfIO $ encrypt mb5 bs128 + , bench "5 MB + 5 MB" $ nfIO $ encrypt mb5 mb5 + ] + , bgroup "aead + open" + [ bench "128 B + 128 B" $ nfIO $ decrypt bs128 bs128 + , bench "128 B + 5 MB" $ nfIO $ decrypt bs128 mb5 + , bench "1 MB + 128 B" $ nfIO $ decrypt mb1 bs128 + , bench "1 MB + 5 B" $ nfIO $ decrypt mb1 mb5 + , bench "5 MB + 128 B" $ nfIO $ decrypt mb5 bs128 + , bench "5 MB + 5 MB" $ nfIO $ decrypt mb5 mb5 + ] + , bgroup "aeadDetached" + [ bench "128 B + 128 B" $ nfIO $ encryptDetached bs128 bs128 + , bench "128 B + 5 MB" $ nfIO $ encryptDetached bs128 mb5 + , bench "1 MB + 128 B" $ nfIO $ encryptDetached mb1 bs128 + , bench "1 MB + 5 B" $ nfIO $ encryptDetached mb1 mb5 + , bench "5 MB + 128 B" $ nfIO $ encryptDetached mb5 bs128 + , bench "5 MB + 5 MB" $ nfIO $ encryptDetached mb5 mb5 + ] + , bgroup "aeadDetached + openDetached" + [ bench "128 B + 128 B" $ nfIO $ decryptDetached bs128 bs128 + , bench "128 B + 5 MB" $ nfIO $ decryptDetached bs128 mb5 + , bench "1 MB + 128 B" $ nfIO $ decryptDetached mb1 bs128 + , bench "1 MB + 5 B" $ nfIO $ decryptDetached mb1 mb5 + , bench "5 MB + 128 B" $ nfIO $ decryptDetached mb5 bs128 + , bench "5 MB + 5 MB" $ nfIO $ decryptDetached mb5 mb5 + ] + ] diff --git a/saltine.cabal b/saltine.cabal new file mode 100644 index 00000000..2e2a279e --- /dev/null +++ b/saltine.cabal @@ -0,0 +1,159 @@ +cabal-version: 2.0 + +name: saltine +version: 0.2.1.0 +synopsis: Cryptography that's easy to digest (NaCl/libsodium bindings). +description: + + /NaCl/ (pronounced \"salt\") is a new easy-to-use high-speed software + library for network communication, encryption, decryption, + signatures, etc. NaCl's goal is to provide all of the core + operations needed to build higher-level cryptographic tools. + . + + . + /Sodium/ is a portable, cross-compilable, installable, packageable + crypto library based on NaCl, with a compatible API. + . + + . + /Saltine/ is a Haskell binding to the NaCl primitives going through + Sodium for build convenience and, eventually, portability. + +extra-source-files: + README.md + CHANGELOG.md + +license: MIT +license-file: LICENSE +author: Joseph Abrahamson +maintainer: Max Amanshauser +bug-reports: http://github.com/tel/saltine/issues +copyright: Copyright (c) Joseph Abrahamson 2013 +category: Cryptography +build-type: Simple +tested-with: GHC==8.0.2, GHC==8.2.2, GHC==8.4.4, GHC==8.6.5, GHC==8.8.4, GHC==8.10.7, GHC==9.0.2, GHC==9.2.6, GHC==9.4.4 + +source-repository head + type: git + location: https://github.com/tel/saltine.git + +library + hs-source-dirs: src + exposed-modules: + Crypto.Saltine + Crypto.Saltine.Class + Crypto.Saltine.Core.SecretBox + Crypto.Saltine.Core.AEAD + Crypto.Saltine.Core.AEAD.AES256GCM + Crypto.Saltine.Core.AEAD.ChaCha20Poly1305 + Crypto.Saltine.Core.AEAD.ChaCha20Poly1305IETF + Crypto.Saltine.Core.AEAD.XChaCha20Poly1305 + Crypto.Saltine.Core.Box + Crypto.Saltine.Core.Stream + Crypto.Saltine.Core.Auth + Crypto.Saltine.Core.OneTimeAuth + Crypto.Saltine.Core.Sign + Crypto.Saltine.Core.Hash + Crypto.Saltine.Core.ScalarMult + Crypto.Saltine.Core.Password + Crypto.Saltine.Core.Utils + Crypto.Saltine.Internal.AEAD.AES256GCM + Crypto.Saltine.Internal.AEAD.ChaCha20Poly1305 + Crypto.Saltine.Internal.AEAD.ChaCha20Poly1305IETF + Crypto.Saltine.Internal.AEAD.XChaCha20Poly1305 + Crypto.Saltine.Internal.Auth + Crypto.Saltine.Internal.Box + Crypto.Saltine.Internal.ByteSizes + Crypto.Saltine.Internal.Hash + Crypto.Saltine.Internal.OneTimeAuth + Crypto.Saltine.Internal.Password + Crypto.Saltine.Internal.ScalarMult + Crypto.Saltine.Internal.SecretBox + Crypto.Saltine.Internal.Sign + Crypto.Saltine.Internal.Stream + Crypto.Saltine.Internal.Util + other-modules: + + if os(windows) + extra-libraries: sodium + else + pkgconfig-depends: libsodium >= 1.0.18 + + cc-options: -Wall + ghc-options: -Wall -funbox-strict-fields + default-language: Haskell2010 + build-depends: + base >= 4.5 && < 5 + , bytestring >= 0.10.8 && < 0.12 + , deepseq ^>= 1.4 + , profunctors >= 5.3 && < 5.7 + , hashable + , text ^>= 1.2 || ^>= 2.0 + +test-suite tests + type: exitcode-stdio-1.0 + main-is: Main.hs + other-modules: + AuthProperties + BoxProperties + HashProperties + OneTimeAuthProperties + PasswordProperties + ScalarMultProperties + SecretBoxProperties + SealedBoxProperties + SignProperties + StreamProperties + AEAD.AES256GCMProperties + AEAD.ChaCha20Poly1305IETFProperties + AEAD.ChaCha20Poly1305Properties + AEAD.XChaCha20Poly1305Properties + Util + UtilProperties + ghc-options: -Wall -threaded -rtsopts + hs-source-dirs: tests + default-language: Haskell2010 + build-depends: + base >= 4.7 && < 5 + , saltine + , bytestring + , text + , QuickCheck + , test-framework-quickcheck2 + , test-framework + , semigroups + +benchmark benchmarks + type: exitcode-stdio-1.0 + main-is: Main.hs + hs-source-dirs: + bench + ghc-options: -rtsopts -threaded -with-rtsopts=-N -O2 + extra-libraries: + sodium + build-depends: + base + , bytestring + , text + , criterion + , deepseq + , saltine + other-modules: + AuthBench + OneTimeAuthBench + ConstantTimeBench + BoxBench + SecretBoxBench + HashBench + RandomBench + PasswordBench + ScalarMultBench + SignBench + StreamBench + BenchUtils + AES256GCMBench + ChaCha20Poly1305Bench + ChaCha20Poly1305IETFBench + XChaCha20Poly1305Bench + default-language: Haskell2010 diff --git a/src/Crypto/Saltine.hs b/src/Crypto/Saltine.hs new file mode 100644 index 00000000..30212479 --- /dev/null +++ b/src/Crypto/Saltine.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE ForeignFunctionInterface #-} +{-# LANGUAGE TypeFamilies #-} + +module Crypto.Saltine ( + sodiumInit + ) where + +import Foreign.C + +-- | Runs Sodiums's initialization routine. This must be called before +-- using any other function. It is thread-safe since libsodium 1.0.11. +sodiumInit :: IO () +sodiumInit = do + err <- c_sodiumInit + case err of + 0 -> -- everything went well + return () + 1 -> -- already initialized, we're good + return () + _ -> -- some kind of failure + error "Crypto.Saltine.sodiumInit" + +foreign import ccall "sodium_init" c_sodiumInit :: IO CInt diff --git a/src/Crypto/Saltine/Class.hs b/src/Crypto/Saltine/Class.hs new file mode 100644 index 00000000..a5f421d3 --- /dev/null +++ b/src/Crypto/Saltine/Class.hs @@ -0,0 +1,55 @@ +-- {-# LANGUAGE FlexibleInstances #-} + +-- | +-- Module : Crypto.Saltine.Class +-- Copyright : (c) Joseph Abrahamson 2013 +-- License : MIT +-- +-- Maintainer : me@jspha.com +-- Stability : experimental +-- Portability : non-portable +-- +-- Saltine type classes +module Crypto.Saltine.Class ( + IsEncoding (..), + IsNonce (..) + ) where + +import Data.Profunctor +import Data.ByteString (ByteString) + +-- | Class for all keys and nonces in Saltine which have a +-- representation as ByteString. 'encoded' is a 'Prism' of +-- type @Prism' ByteString a@ compatible with "Control.Lens" and +-- is automatically deduced. +class IsEncoding a where + encode :: a -> ByteString + decode :: ByteString -> Maybe a + encoded :: (Choice p, Applicative f) + => p a (f a) -> p ByteString (f ByteString) + encoded = prism' encode decode + {-# INLINE encoded #-} + +-- | A generic class for interacting with nonces. +class IsNonce n where + zero :: n + -- ^ Some privileged nonce value. + nudge :: n -> n + -- ^ Some perturbation on nonces such that @n /= nudge n@ with high + -- probability. Since nonces are finite, repeats may happen in + -- particularly small cases, but no nonces in Saltine are so + -- small. This is not guaranteed to be difficult to predict---if a + -- nonce had an `Enum` instance `succ` would be a good + -- implementation excepting that `succ` is partial. + +-- Copied over from Control.Lens + +prism' :: (Applicative f, Choice p) => + (a1 -> a) -> (a -> Maybe a2) -> p a2 (f a1) -> p a (f a) +prism' bs sma = prism bs (\s -> maybe (Left s) Right (sma s)) +{-# INLINE prism' #-} + +prism :: (Applicative f, Choice p) => + (a2 -> a1) -> (a -> Either a1 a3) -> p a3 (f a2) -> p a (f a1) +prism bt seta = dimap seta (either pure (fmap bt)) . right' +{-# INLINE prism #-} diff --git a/src/Crypto/Saltine/Core/AEAD.hs b/src/Crypto/Saltine/Core/AEAD.hs new file mode 100644 index 00000000..d258de5e --- /dev/null +++ b/src/Crypto/Saltine/Core/AEAD.hs @@ -0,0 +1,47 @@ +-- | +-- Module : Crypto.Saltine.Core.AEAD +-- Copyright : (c) Thomas DuBuisson 2017 +-- (c) Max Amanshauser 2021 +-- License : MIT +-- +-- Maintainer : max@lambdalifting.org +-- Stability : experimental +-- Portability : non-portable +-- +-- Secret-key authenticated encryption with additional data (AEAD): +-- "Crypto.Saltine.Core.AEAD" +-- +-- The 'aead' function encrypts and authenticates a message +-- 'ByteString' and additional authenticated data 'ByteString' +-- using a secret key and a nonce. The 'aeadOpen' +-- function verifies and decrypts a ciphertext 'ByteString' using a +-- secret key and a nonce. If the ciphertext fails validation, +-- 'aeadOpen' returns 'Nothing'. +-- +-- The "Crypto.Saltine.Core.AEAD" module is designed to meet +-- the standard notions of privacy and authenticity for a secret-key +-- authenticated-encryption scheme using nonces. For formal +-- definitions see, e.g., Bellare and Namprempre, "Authenticated +-- encryption: relations among notions and analysis of the generic +-- composition paradigm," Lecture Notes in Computer Science 1976 +-- (2000), 531–545, . +-- +-- Note that the length is not hidden. Note also that it is the +-- caller's responsibility to ensure the uniqueness of nonces—for +-- example, by using nonce 1 for the first message, nonce 2 for the +-- second message, etc. With XChaCha20Poly1305 nonces are long enough +-- that you can also generate nonces randomly as they have negligible +-- risk of collision. +-- +-- The keysize is identical for all the *ChaCha20Poly1305* variants, +-- but the nonce length differs. Since libsodium keeps separate definitions, +-- we do the same. +-- +-- This module reexports the XChaCha20Poly1305 variant, which is the +-- recommended one. + +module Crypto.Saltine.Core.AEAD ( + module X + ) where + +import Crypto.Saltine.Core.AEAD.XChaCha20Poly1305 as X diff --git a/src/Crypto/Saltine/Core/AEAD/AES256GCM.hs b/src/Crypto/Saltine/Core/AEAD/AES256GCM.hs new file mode 100644 index 00000000..626246f9 --- /dev/null +++ b/src/Crypto/Saltine/Core/AEAD/AES256GCM.hs @@ -0,0 +1,149 @@ +-- | +-- Module : Crypto.Saltine.Core.AEAD.AES256GCM +-- Copyright : (c) Thomas DuBuisson 2017 +-- (c) Max Amanshauser 2021 +-- License : MIT +-- +-- Maintainer : max@lambdalifting.org +-- Stability : experimental +-- Portability : non-portable +-- +-- Secret-key authenticated encryption with additional data (AEAD): +-- "Crypto.Saltine.Core.AEAD.AES256GCM" +-- +-- Using this module is not recommended. Don't use unless you have to. +-- Keep in mind its limitations: https://doc.libsodium.org/secret-key_cryptography/aead +-- +-- Unless you know for certain the CPU your program will run on supports +-- Intel SSSE3, AES-NI and CLMUL, you should run @aead_aes256gcm_available@ +-- first and only proceed if the result is True. +-- +-- Generating nonces for the functions in this module randomly +-- is not recommended, due to the risk of generating collisions. + +module Crypto.Saltine.Core.AEAD.AES256GCM ( + Key, Nonce, + aead_aes256gcm_available, + aead, aeadOpen, + aeadDetached, aeadOpenDetached, + newKey, newNonce + ) where + +import Crypto.Saltine.Internal.AEAD.AES256GCM + ( c_aead_aes256gcm_is_available + , c_aead + , c_aead_open + , c_aead_detached + , c_aead_open_detached + , Key(..) + , Nonce(..) + ) +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) +import Foreign.Ptr +import System.IO.Unsafe (unsafePerformIO) + +import qualified Crypto.Saltine.Internal.AEAD.AES256GCM as Bytes +import qualified Data.ByteString as S + +-- | Creates a random 'AES256GCM' key +newKey :: IO Key +newKey = Key <$> randomByteString Bytes.aead_aes256gcm_keybytes + +-- | Creates a random 'AES256GCM' nonce +newNonce :: IO Nonce +newNonce = Nonce <$> randomByteString Bytes.aead_aes256gcm_npubbytes + + +{-# NOINLINE aead_aes256gcm_available #-} +aead_aes256gcm_available :: Bool +aead_aes256gcm_available = + unsafePerformIO c_aead_aes256gcm_is_available == 1 + +-- | Encrypts a message. It is infeasible for an attacker to decrypt +-- the message so long as the 'Nonce' is never repeated. +aead + :: Key + -> Nonce + -> ByteString + -- ^ Message + -> ByteString + -- ^ AAD + -> ByteString + -- ^ Ciphertext +aead (Key key) (Nonce nonce) msg aad = + snd . buildUnsafeByteString clen $ \pc -> + constByteStrings [key, msg, aad, nonce] $ \ + [(pk, _), (pm, _), (pa, _), (pn, _)] -> + c_aead pc nullPtr pm (fromIntegral mlen) pa (fromIntegral alen) nullPtr pn pk + where mlen = S.length msg + alen = S.length aad + clen = mlen + Bytes.aead_aes256gcm_abytes + +-- | Decrypts a message. Returns 'Nothing' if the keys and message do +-- not match. +aeadOpen + :: Key + -> Nonce + -> ByteString + -- ^ Ciphertext + -> ByteString + -- ^ AAD + -> Maybe ByteString + -- ^ Message +aeadOpen (Key key) (Nonce nonce) cipher aad = do + let clen = S.length cipher + alen = S.length aad + mlen <- clen `safeSubtract` Bytes.aead_aes256gcm_abytes + let (err, vec) = buildUnsafeByteString mlen $ \pm -> + constByteStrings [key, cipher, aad, nonce] $ \ + [(pk, _), (pc, _), (pa, _), (pn, _)] -> + c_aead_open pm nullPtr nullPtr pc (fromIntegral clen) pa (fromIntegral alen) pn pk + hush . handleErrno err $ vec + +-- | Encrypts a message. It is infeasible for an attacker to decrypt +-- the message so long as the 'Nonce' is never repeated. +aeadDetached + :: Key + -> Nonce + -> ByteString + -- ^ Message + -> ByteString + -- ^ AAD + -> (ByteString,ByteString) + -- ^ Tag, Ciphertext +aeadDetached (Key key) (Nonce nonce) msg aad = + buildUnsafeByteString clen $ \pc -> + fmap snd . buildUnsafeByteString' tlen $ \pt -> + constByteStrings [key, msg, aad, nonce] $ \ + [(pk, _), (pm, _), (pa, _), (pn, _)] -> + c_aead_detached pc pt nullPtr pm (fromIntegral mlen) pa (fromIntegral alen) nullPtr pn pk + where mlen = S.length msg + alen = S.length aad + clen = mlen + tlen = Bytes.aead_aes256gcm_abytes + +-- | Decrypts a message. Returns 'Nothing' if the keys and message do +-- not match. +aeadOpenDetached + :: Key + -> Nonce + -> ByteString + -- ^ Tag + -> ByteString + -- ^ Ciphertext + -> ByteString + -- ^ AAD + -> Maybe ByteString + -- ^ Message +aeadOpenDetached (Key key) (Nonce nonce) tag cipher aad + | S.length tag /= tlen = Nothing + | otherwise = + let (err, vec) = buildUnsafeByteString len $ \pm -> + constByteStrings [key, tag, cipher, aad, nonce] $ \ + [(pk, _), (pt, _), (pc, _), (pa, _), (pn, _)] -> + c_aead_open_detached pm nullPtr pc (fromIntegral len) pt pa (fromIntegral alen) pn pk + in hush . handleErrno err $ vec + where len = S.length cipher + alen = S.length aad + tlen = Bytes.aead_aes256gcm_abytes diff --git a/src/Crypto/Saltine/Core/AEAD/ChaCha20Poly1305.hs b/src/Crypto/Saltine/Core/AEAD/ChaCha20Poly1305.hs new file mode 100644 index 00000000..0de8a45e --- /dev/null +++ b/src/Crypto/Saltine/Core/AEAD/ChaCha20Poly1305.hs @@ -0,0 +1,134 @@ +-- | +-- Module : Crypto.Saltine.Core.AEAD.ChaCha20Poly1305 +-- Copyright : (c) Thomas DuBuisson 2017 +-- (c) Max Amanshauser 2021 +-- License : MIT +-- +-- Maintainer : max@lambdalifting.org +-- Stability : experimental +-- Portability : non-portable +-- +-- Secret-key authenticated encryption with additional data (AEAD): +-- "Crypto.Saltine.Core.AEAD.ChaCha20Poly1305" +-- +-- Generating nonces for the functions in this module randomly +-- is not recommended, due to the risk of generating collisions. + +module Crypto.Saltine.Core.AEAD.ChaCha20Poly1305 ( + Key, Nonce, + aead, aeadOpen, + aeadDetached, aeadOpenDetached, + newKey, newNonce + ) where + +import Crypto.Saltine.Internal.AEAD.ChaCha20Poly1305 + ( c_aead + , c_aead_open + , c_aead_detached + , c_aead_open_detached + , Key(..) + , Nonce(..) + ) +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) +import Foreign.Ptr + +import qualified Crypto.Saltine.Internal.AEAD.ChaCha20Poly1305 as Bytes +import qualified Data.ByteString as S + +-- | Creates a random 'ChaCha20Poly1305' key +newKey :: IO Key +newKey = Key <$> randomByteString Bytes.aead_chacha20poly1305_keybytes + +-- | Creates a random 'ChaCha20Poly1305' nonce +newNonce :: IO Nonce +newNonce = Nonce <$> randomByteString Bytes.aead_chacha20poly1305_npubbytes + + +-- | Encrypts a message. It is infeasible for an attacker to decrypt +-- the message so long as the 'Nonce' is never repeated. +aead + :: Key + -> Nonce + -> ByteString + -- ^ Message + -> ByteString + -- ^ AAD + -> ByteString + -- ^ Ciphertext +aead (Key key) (Nonce nonce) msg aad = + snd . buildUnsafeByteString clen $ \pc -> + constByteStrings [key, msg, aad, nonce] $ \ + [(pk, _), (pm, _), (pa, _), (pn, _)] -> + c_aead pc nullPtr pm (fromIntegral mlen) pa (fromIntegral alen) nullPtr pn pk + where mlen = S.length msg + alen = S.length aad + clen = mlen + Bytes.aead_chacha20poly1305_abytes + +-- | Decrypts a message. Returns 'Nothing' if the keys and message do +-- not match. +aeadOpen + :: Key + -> Nonce + -> ByteString + -- ^ Ciphertext + -> ByteString + -- ^ AAD + -> Maybe ByteString + -- ^ Message +aeadOpen (Key key) (Nonce nonce) cipher aad = do + let clen = S.length cipher + alen = S.length aad + mlen <- clen `safeSubtract` Bytes.aead_chacha20poly1305_abytes + let (err, vec) = buildUnsafeByteString mlen $ \pm -> + constByteStrings [key, cipher, aad, nonce] $ \ + [(pk, _), (pc, _), (pa, _), (pn, _)] -> + c_aead_open pm nullPtr nullPtr pc (fromIntegral clen) pa (fromIntegral alen) pn pk + hush . handleErrno err $ vec + +-- | Encrypts a message. It is infeasible for an attacker to decrypt +-- the message so long as the 'Nonce' is never repeated. +aeadDetached + :: Key + -> Nonce + -> ByteString + -- ^ Message + -> ByteString + -- ^ AAD + -> (ByteString,ByteString) + -- ^ Tag, Ciphertext +aeadDetached (Key key) (Nonce nonce) msg aad = + buildUnsafeByteString clen $ \pc -> + fmap snd . buildUnsafeByteString' tlen $ \pt -> + constByteStrings [key, msg, aad, nonce] $ \ + [(pk, _), (pm, _), (pa, _), (pn, _)] -> + c_aead_detached pc pt nullPtr pm (fromIntegral mlen) pa (fromIntegral alen) nullPtr pn pk + where mlen = S.length msg + alen = S.length aad + clen = mlen + tlen = Bytes.aead_chacha20poly1305_abytes + +-- | Decrypts a message. Returns 'Nothing' if the keys and message do +-- not match. +aeadOpenDetached + :: Key + -> Nonce + -> ByteString + -- ^ Tag + -> ByteString + -- ^ Ciphertext + -> ByteString + -- ^ AAD + -> Maybe ByteString + -- ^ Message +aeadOpenDetached (Key key) (Nonce nonce) tag cipher aad + | S.length tag /= tlen = Nothing + | otherwise = + let (err, vec) = buildUnsafeByteString len $ \pm -> + constByteStrings [key, tag, cipher, aad, nonce] $ \ + [(pk, _), (pt, _), (pc, _), (pa, _), (pn, _)] -> + c_aead_open_detached pm nullPtr pc (fromIntegral len) pt pa (fromIntegral alen) pn pk + in hush . handleErrno err $ vec + where len = S.length cipher + alen = S.length aad + tlen = Bytes.aead_chacha20poly1305_abytes diff --git a/src/Crypto/Saltine/Core/AEAD/ChaCha20Poly1305IETF.hs b/src/Crypto/Saltine/Core/AEAD/ChaCha20Poly1305IETF.hs new file mode 100644 index 00000000..07f10f4d --- /dev/null +++ b/src/Crypto/Saltine/Core/AEAD/ChaCha20Poly1305IETF.hs @@ -0,0 +1,134 @@ +-- | +-- Module : Crypto.Saltine.Core.AEAD.ChaCha20Poly1305IETF +-- Copyright : (c) Thomas DuBuisson 2017 +-- (c) Max Amanshauser 2021 +-- License : MIT +-- +-- Maintainer : max@lambdalifting.org +-- Stability : experimental +-- Portability : non-portable +-- +-- Secret-key authenticated encryption with additional data (AEAD): +-- "Crypto.Saltine.Core.AEAD.ChaCha20Poly1305IETF" +-- +-- Generating nonces for the functions in this module randomly +-- is not recommended, due to the risk of generating collisions. + +module Crypto.Saltine.Core.AEAD.ChaCha20Poly1305IETF ( + Key, Nonce, + aead, aeadOpen, + aeadDetached, aeadOpenDetached, + newKey, newNonce + ) where + +import Crypto.Saltine.Internal.AEAD.ChaCha20Poly1305IETF + ( c_aead + , c_aead_open + , c_aead_detached + , c_aead_open_detached + , Key(..) + , Nonce(..) + ) +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) +import Foreign.Ptr + +import qualified Crypto.Saltine.Internal.AEAD.ChaCha20Poly1305IETF as Bytes +import qualified Data.ByteString as S + +-- | Creates a random 'ChaCha20Poly1305IETF' key +newKey :: IO Key +newKey = Key <$> randomByteString Bytes.aead_chacha20poly1305_ietf_keybytes + +-- | Creates a random 'ChaCha20Poly1305IETF' nonce +newNonce :: IO Nonce +newNonce = Nonce <$> randomByteString Bytes.aead_chacha20poly1305_ietf_npubbytes + + +-- | Encrypts a message. It is infeasible for an attacker to decrypt +-- the message so long as the 'Nonce' is never repeated. +aead + :: Key + -> Nonce + -> ByteString + -- ^ Message + -> ByteString + -- ^ AAD + -> ByteString + -- ^ Ciphertext +aead (Key key) (Nonce nonce) msg aad = + snd . buildUnsafeByteString clen $ \pc -> + constByteStrings [key, msg, aad, nonce] $ \ + [(pk, _), (pm, _), (pa, _), (pn, _)] -> + c_aead pc nullPtr pm (fromIntegral mlen) pa (fromIntegral alen) nullPtr pn pk + where mlen = S.length msg + alen = S.length aad + clen = mlen + Bytes.aead_chacha20poly1305_ietf_abytes + +-- | Decrypts a message. Returns 'Nothing' if the keys and message do +-- not match. +aeadOpen + :: Key + -> Nonce + -> ByteString + -- ^ Ciphertext + -> ByteString + -- ^ AAD + -> Maybe ByteString + -- ^ Message +aeadOpen (Key key) (Nonce nonce) cipher aad = do + let clen = S.length cipher + alen = S.length aad + mlen <- clen `safeSubtract` Bytes.aead_chacha20poly1305_ietf_abytes + let (err, vec) = buildUnsafeByteString mlen $ \pm -> + constByteStrings [key, cipher, aad, nonce] $ \ + [(pk, _), (pc, _), (pa, _), (pn, _)] -> + c_aead_open pm nullPtr nullPtr pc (fromIntegral clen) pa (fromIntegral alen) pn pk + hush . handleErrno err $ vec + +-- | Encrypts a message. It is infeasible for an attacker to decrypt +-- the message so long as the 'Nonce' is never repeated. +aeadDetached + :: Key + -> Nonce + -> ByteString + -- ^ Message + -> ByteString + -- ^ AAD + -> (ByteString,ByteString) + -- ^ Tag, Ciphertext +aeadDetached (Key key) (Nonce nonce) msg aad = + buildUnsafeByteString clen $ \pc -> + fmap snd . buildUnsafeByteString' tlen $ \pt -> + constByteStrings [key, msg, aad, nonce] $ \ + [(pk, _), (pm, _), (pa, _), (pn, _)] -> + c_aead_detached pc pt nullPtr pm (fromIntegral mlen) pa (fromIntegral alen) nullPtr pn pk + where mlen = S.length msg + alen = S.length aad + clen = mlen + tlen = Bytes.aead_chacha20poly1305_ietf_abytes + +-- | Decrypts a message. Returns 'Nothing' if the keys and message do +-- not match. +aeadOpenDetached + :: Key + -> Nonce + -> ByteString + -- ^ Tag + -> ByteString + -- ^ Ciphertext + -> ByteString + -- ^ AAD + -> Maybe ByteString + -- ^ Message +aeadOpenDetached (Key key) (Nonce nonce) tag cipher aad + | S.length tag /= tlen = Nothing + | otherwise = + let (err, vec) = buildUnsafeByteString len $ \pm -> + constByteStrings [key, tag, cipher, aad, nonce] $ \ + [(pk, _), (pt, _), (pc, _), (pa, _), (pn, _)] -> + c_aead_open_detached pm nullPtr pc (fromIntegral len) pt pa (fromIntegral alen) pn pk + in hush . handleErrno err $ vec + where len = S.length cipher + alen = S.length aad + tlen = Bytes.aead_chacha20poly1305_ietf_abytes diff --git a/src/Crypto/Saltine/Core/AEAD/XChaCha20Poly1305.hs b/src/Crypto/Saltine/Core/AEAD/XChaCha20Poly1305.hs new file mode 100644 index 00000000..da055876 --- /dev/null +++ b/src/Crypto/Saltine/Core/AEAD/XChaCha20Poly1305.hs @@ -0,0 +1,134 @@ +-- | +-- Module : Crypto.Saltine.Core.AEAD.XChaCha20Poly1305 +-- Copyright : (c) Thomas DuBuisson 2017 +-- License : MIT +-- +-- Maintainer : max@lambdalifting.org +-- Stability : experimental +-- Portability : non-portable +-- +-- Secret-key authenticated encryption with additional data (AEAD): +-- "Crypto.Saltine.Core.AEAD.XChaCha20Poly1305" +-- +-- Nonces are long enough that randomly generated +-- nonces have negligible risk of collision. + +module Crypto.Saltine.Core.AEAD.XChaCha20Poly1305 ( + Key, Nonce, + aead, aeadOpen, + aeadDetached, aeadOpenDetached, + newKey, newNonce + ) where + +import Crypto.Saltine.Internal.AEAD.XChaCha20Poly1305 + ( c_aead + , c_aead_open + , c_aead_detached + , c_aead_open_detached + , Key(..) + , Nonce(..) + ) +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) +import Foreign.Ptr + +import qualified Crypto.Saltine.Internal.AEAD.XChaCha20Poly1305 as Bytes +import qualified Data.ByteString as S + + +-- | Creates a random 'XChaCha20Poly1305' key +newKey :: IO Key +newKey = Key <$> randomByteString Bytes.aead_xchacha20poly1305_ietf_keybytes + +-- | Creates a random 'XChaCha20Poly1305' nonce +newNonce :: IO Nonce +newNonce = Nonce <$> randomByteString Bytes.aead_xchacha20poly1305_ietf_npubbytes + + +-- | Encrypts a message. It is infeasible for an attacker to decrypt +-- the message so long as the 'Nonce' is never repeated. +aead + :: Key + -> Nonce + -> ByteString + -- ^ Message + -> ByteString + -- ^ AAD + -> ByteString + -- ^ Ciphertext +aead (Key key) (Nonce nonce) msg aad = + snd . buildUnsafeByteString clen $ \pc -> + constByteStrings [key, msg, aad, nonce] $ \ + [(pk, _), (pm, _), (pa, _), (pn, _)] -> + c_aead pc nullPtr pm (fromIntegral mlen) pa (fromIntegral alen) nullPtr pn pk + where mlen = S.length msg + alen = S.length aad + clen = mlen + Bytes.aead_xchacha20poly1305_ietf_abytes + +-- | Decrypts a message. Returns 'Nothing' if the keys and message do +-- not match. +aeadOpen + :: Key + -> Nonce + -> ByteString + -- ^ Ciphertext + -> ByteString + -- ^ AAD + -> Maybe ByteString + -- ^ Message +aeadOpen (Key key) (Nonce nonce) cipher aad = do + let clen = S.length cipher + alen = S.length aad + mlen <- clen `safeSubtract` Bytes.aead_xchacha20poly1305_ietf_abytes + let (err, vec) = buildUnsafeByteString mlen $ \pm -> + constByteStrings [key, cipher, aad, nonce] $ \ + [(pk, _), (pc, _), (pa, _), (pn, _)] -> + c_aead_open pm nullPtr nullPtr pc (fromIntegral clen) pa (fromIntegral alen) pn pk + hush . handleErrno err $ vec + +-- | Encrypts a message. It is infeasible for an attacker to decrypt +-- the message so long as the 'Nonce' is never repeated. +aeadDetached + :: Key + -> Nonce + -> ByteString + -- ^ Message + -> ByteString + -- ^ AAD + -> (ByteString,ByteString) + -- ^ Tag, Ciphertext +aeadDetached (Key key) (Nonce nonce) msg aad = + buildUnsafeByteString clen $ \pc -> + fmap snd . buildUnsafeByteString' tlen $ \pt -> + constByteStrings [key, msg, aad, nonce] $ \ + [(pk, _), (pm, _), (pa, _), (pn, _)] -> + c_aead_detached pc pt nullPtr pm (fromIntegral mlen) pa (fromIntegral alen) nullPtr pn pk + where mlen = S.length msg + alen = S.length aad + clen = mlen + tlen = Bytes.aead_xchacha20poly1305_ietf_abytes + +-- | Decrypts a message. Returns 'Nothing' if the keys and message do +-- not match. +aeadOpenDetached + :: Key + -> Nonce + -> ByteString + -- ^ Tag + -> ByteString + -- ^ Ciphertext + -> ByteString + -- ^ AAD + -> Maybe ByteString + -- ^ Message +aeadOpenDetached (Key key) (Nonce nonce) tag cipher aad + | S.length tag /= tlen = Nothing + | otherwise = + let (err, vec) = buildUnsafeByteString len $ \pm -> + constByteStrings [key, tag, cipher, aad, nonce] $ \ + [(pk, _), (pt, _), (pc, _), (pa, _), (pn, _)] -> + c_aead_open_detached pm nullPtr pc (fromIntegral len) pt pa (fromIntegral alen) pn pk + in hush . handleErrno err $ vec + where len = S.length cipher + alen = S.length aad + tlen = Bytes.aead_xchacha20poly1305_ietf_abytes diff --git a/src/Crypto/Saltine/Core/Auth.hs b/src/Crypto/Saltine/Core/Auth.hs new file mode 100644 index 00000000..00dc0aaa --- /dev/null +++ b/src/Crypto/Saltine/Core/Auth.hs @@ -0,0 +1,84 @@ +-- | +-- Module : Crypto.Saltine.Core.Auth +-- Copyright : (c) Joseph Abrahamson 2013 +-- License : MIT +-- +-- Maintainer : me@jspha.com +-- Stability : experimental +-- Portability : non-portable +-- +-- Secret-key message authentication: +-- "Crypto.Saltine.Core.Auth" +-- +-- The 'auth' function authenticates a message 'ByteString' using a +-- secret key. The function returns an authenticator. The 'verify' +-- function checks if it's passed a correct authenticator of a message +-- under the given secret key. +-- +-- The 'auth' function, viewed as a function of the message for a +-- uniform random key, is designed to meet the standard notion of +-- unforgeability. This means that an attacker cannot find +-- authenticators for any messages not authenticated by the sender, +-- even if the attacker has adaptively influenced the messages +-- authenticated by the sender. For a formal definition see, e.g., +-- Section 2.4 of Bellare, Kilian, and Rogaway, \"The security of the +-- cipher block chaining message authentication code,\" Journal of +-- Computer and System Sciences 61 (2000), 362–399; +-- . +-- +-- Saltine does not make any promises regarding \"strong\" +-- unforgeability; perhaps one valid authenticator can be converted +-- into another valid authenticator for the same message. NaCl also +-- does not make any promises regarding \"truncated unforgeability.\" +-- +-- "Crypto.Saltine.Core.Auth" is currently an implementation of +-- HMAC-SHA-512-256, i.e., the first 256 bits of +-- HMAC-SHA-512. HMAC-SHA-512-256 is conjectured to meet the standard +-- notion of unforgeability. +-- +-- This is version 2010.08.30 of the auth.html web page. +module Crypto.Saltine.Core.Auth ( + Key, Authenticator, + newKey, + auth, verify + ) where + +import Crypto.Saltine.Internal.Auth + ( c_auth + , c_auth_verify + , Key(..) + , Authenticator(..) + ) +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) + +import qualified Crypto.Saltine.Internal.Auth as Bytes + +-- | Creates a random key of the correct size for 'auth' and 'verify'. +newKey :: IO Key +newKey = Key <$> randomByteString Bytes.auth_keybytes + +-- | Computes an keyed authenticator 'ByteString' from a message. It +-- is infeasible to forge these authenticators without the key, even +-- if an attacker observes many authenticators and messages and has +-- the ability to influence the messages sent. +auth :: Key + -> ByteString + -- ^ Message + -> Authenticator +auth (Key key) msg = + Au . snd . buildUnsafeByteString Bytes.auth_bytes $ \pa -> + constByteStrings [key, msg] $ \[(pk, _), (pm, mlen)] -> + c_auth pa pm (fromIntegral mlen) pk + +-- | Checks to see if an authenticator is a correct proof that a +-- message was signed by some key. +verify :: Key + -> Authenticator + -> ByteString + -- ^ Message + -> Bool + -- ^ Is this message authentic? +verify (Key key) (Au a) msg = + unsafeDidSucceed $ constByteStrings [key, msg, a] $ \[(pk, _), (pm, mlen), (pa, _)] -> + return $ c_auth_verify pa pm (fromIntegral mlen) pk diff --git a/src/Crypto/Saltine/Core/Box.hs b/src/Crypto/Saltine/Core/Box.hs new file mode 100644 index 00000000..bf659f15 --- /dev/null +++ b/src/Crypto/Saltine/Core/Box.hs @@ -0,0 +1,231 @@ +-- | +-- Module : Crypto.Saltine.Core.Box +-- Copyright : (c) Joseph Abrahamson 2013 +-- License : MIT +-- +-- Maintainer : me@jspha.com +-- Stability : experimental +-- Portability : non-portable +-- +-- Public-key cryptography abstraction: +-- "Crypto.Saltine.Core.Box" +-- +-- This module consists of functions dealing with two public-key +-- cryptography concepts in libsodium. +-- +-- The first one is an authenticated encryption scheme. In this +-- scheme, the 'box' function encrypts and authenticates a message +-- 'ByteString' using the sender's secret key, the receiver's public +-- key, and a nonce. The 'boxOpen' function verifies and decrypts a +-- ciphertext 'ByteString' using the receiver's secret key, the +-- sender's public key, and a nonce. If the ciphertext fails +-- verification, 'boxOpen' returns 'Nothing'. +-- +-- The set of box functions is designed to meet the +-- standard notions of privacy and third-party unforgeability for a +-- public-key authenticated-encryption scheme using nonces. For formal +-- definitions see, e.g., Jee Hea An, "Authenticated encryption in the +-- public-key setting: security notions and analyses," +-- . +-- +-- Distinct messages between the same @{sender, receiver}@ set are +-- required to have distinct nonces. For example, the +-- lexicographically smaller public key can use nonce 1 for its first +-- message to the other key, nonce 3 for its second message, nonce 5 +-- for its third message, etc., while the lexicographically larger +-- public key uses nonce 2 for its first message to the other key, +-- nonce 4 for its second message, nonce 6 for its third message, +-- etc. Nonces are long enough that randomly generated nonces have +-- negligible risk of collision. +-- +-- There is no harm in having the same nonce for different messages if +-- the @{sender, receiver}@ sets are different. This is true even if +-- the sets overlap. For example, a sender can use the same nonce for +-- two different messages if the messages are sent to two different +-- public keys. +-- +-- The second concept is sealed boxes, which provide encryption and +-- preservation of integrity, but not authentication. Technically, +-- the sender of a message generates a keypair, uses the regular +-- box mechanism, attaches the public key to the message and then +-- immediately destroys the private key. This is useful, e.g. when +-- the receiver cannot know the sender's public key in advance and +-- hence cannot use the regular box functions, or when you want to +-- send messages anonymously. +-- +-- The "Crypto.Saltine.Core.Box" module is not meant to provide +-- non-repudiation. On the contrary: the crypto_box function +-- guarantees repudiability. A receiver can freely modify a boxed +-- message, and therefore cannot convince third parties that this +-- particular message came from the sender. The sender and receiver +-- are nevertheless protected against forgeries by other parties. In +-- the terminology of +-- , +-- crypto_box uses "public-key authenticators" rather than "public-key +-- signatures." +-- +-- Users who want public verifiability (or receiver-assisted public +-- verifiability) should instead use signatures (or +-- signcryption). Signatures are documented in the +-- "Crypto.Saltine.Core.Sign" module. +-- +-- "Crypto.Saltine.Core.Box" is @curve25519xsalsa20poly1305@, a +-- particular combination of Curve25519, Salsa20, and Poly1305 +-- specified in "Cryptography in NaCl" +-- (). This function is conjectured +-- to meet the standard notions of privacy and third-party +-- unforgeability. +-- +-- This is version 2010.08.30 of the box.html web page. +module Crypto.Saltine.Core.Box ( + SecretKey, PublicKey, Keypair(..), CombinedKey, Nonce, + newKeypair, beforeNM, newNonce, + box, boxOpen, + boxAfterNM, boxOpenAfterNM, + boxSeal, boxSealOpen + ) where + +import Crypto.Saltine.Internal.Box + ( c_box_keypair + , c_box_easy + , c_box_open_easy + , c_box_beforenm + , c_box_easy_afternm + , c_box_open_easy_afternm + , c_box_seal, c_box_seal_open + , SecretKey(..) + , PublicKey(..) + , Keypair(..) + , CombinedKey(..) + , Nonce(..) + ) +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) + +import qualified Crypto.Saltine.Internal.Box as Bytes +import qualified Data.ByteString as S + +-- | Randomly generates a secret key and a corresponding public key. +newKeypair :: IO Keypair +newKeypair = do + -- This is a little bizarre and a likely source of errors. + -- _err ought to always be 0. + ((_err, sk), pk) <- buildUnsafeByteString' Bytes.box_publickeybytes $ \pkbuf -> + buildUnsafeByteString' Bytes.box_secretkeybytes $ \skbuf -> + c_box_keypair pkbuf skbuf + return $ Keypair (SK sk) (PK pk) + +-- | Randomly generates a nonce for usage with 'box' and 'boxOpen'. +newNonce :: IO Nonce +newNonce = Nonce <$> randomByteString Bytes.box_noncebytes + +-- | Build a 'CombinedKey' for sending from 'SecretKey' to +-- 'PublicKey'. This is a precomputation step which can accelerate +-- later encryption calls. +beforeNM :: SecretKey -> PublicKey -> CombinedKey +beforeNM (SK sk) (PK pk) = CK $ snd $ buildUnsafeByteString Bytes.box_beforenmbytes $ \ckbuf -> + constByteStrings [pk, sk] $ \[(ppk, _), (psk, _)] -> + c_box_beforenm ckbuf ppk psk + +-- | Encrypts a message for sending to the owner of the public +-- key. They must have your public key in order to decrypt the +-- message. It is infeasible for an attacker to decrypt the message so +-- long as the 'Nonce' is not repeated. +box :: PublicKey + -> SecretKey + -> Nonce + -> ByteString + -- ^ Message + -> ByteString + -- ^ Ciphertext (incl. authentication tag) +box (PK pk) (SK sk) (Nonce nonce) msg = + snd . buildUnsafeByteString bufSize $ \pc -> + constByteStrings [pk, sk, msg, nonce] $ \ + [(ppk, _), (psk, _), (pm, _), (pn, _)] -> + c_box_easy pc pm (fromIntegral msgLen) pn ppk psk + where + bufSize = S.length msg + Bytes.box_macbytes + msgLen = S.length msg + +-- | Decrypts a message sent from the owner of the public key. They +-- must have encrypted it using your public key. Returns 'Nothing' if +-- the keys and message do not match. +boxOpen :: PublicKey -> SecretKey -> Nonce + -> ByteString + -- ^ Ciphertext (incl. authentication tag) + -> Maybe ByteString + -- ^ Message +boxOpen (PK pk) (SK sk) (Nonce nonce) cipher = do + let msgLen = S.length cipher + bufSize <- msgLen `safeSubtract` Bytes.box_macbytes + let (err, vec) = buildUnsafeByteString bufSize $ \pm -> + constByteStrings [pk, sk, cipher, nonce] $ \ + [(ppk, _), (psk, _), (pc, _), (pn, _)] -> + c_box_open_easy pm pc (fromIntegral msgLen) pn ppk psk + hush . handleErrno err $ vec + + +-- | 'box' using a 'CombinedKey' and thus faster. +boxAfterNM :: CombinedKey + -> Nonce + -> ByteString + -- ^ Message + -> ByteString + -- ^ Ciphertext (incl. authentication tag) +boxAfterNM (CK ck) (Nonce nonce) msg = + snd . buildUnsafeByteString bufSize $ \pc -> + constByteStrings [ck, msg, nonce] $ \ + [(pck, _), (pm, _), (pn, _)] -> + c_box_easy_afternm pc pm (fromIntegral msgLen) pn pck + where + bufSize = S.length msg + Bytes.box_macbytes + msgLen = S.length msg + +-- | 'boxOpen' using a 'CombinedKey' and is thus faster. +boxOpenAfterNM :: CombinedKey + -> Nonce + -> ByteString + -- ^ Ciphertext (incl. authentication tag) + -> Maybe ByteString + -- ^ Message +boxOpenAfterNM (CK ck) (Nonce nonce) cipher = do + let msgLen = S.length cipher + bufSize <- msgLen `safeSubtract` Bytes.box_macbytes + let (err, vec) = buildUnsafeByteString bufSize $ \pm -> + constByteStrings [ck, cipher, nonce] $ \ + [(pck, _), (pc, _), (pn, _)] -> + c_box_open_easy_afternm pm pc (fromIntegral msgLen) pn pck + hush . handleErrno err $ vec + + +-- | Encrypts a message for sending to the owner of the public +-- key. The message is unauthenticated, but permits integrity checking. +-- This function is non-deterministic, it uses newly created ephemeral keys internally, +-- and thus in IO. +boxSeal :: PublicKey -> ByteString -> IO ByteString +boxSeal (PK pk) msg = fmap snd . buildUnsafeByteString' bufSize $ \pc -> + constByteStrings [pk, msg] $ \ + [(ppk, _), (pm, _)] -> + c_box_seal pc pm (fromIntegral msgLen) ppk + where + bufSize = S.length msg + Bytes.box_sealbytes + msgLen = S.length msg + +-- | Decrypts a sealed box message. The message must have been +-- encrypted using the receiver's public key. +-- Returns 'Nothing' if keys and message do not match or integrity +-- is violated. +boxSealOpen :: PublicKey + -> SecretKey + -> ByteString + -- ^ Ciphertext + -> Maybe ByteString + -- ^ Message +boxSealOpen (PK pk) (SK sk) cipher = do + let msgLen = S.length cipher + bufSize <- msgLen `safeSubtract` Bytes.box_sealbytes + let (err, vec) = buildUnsafeByteString bufSize $ \pm -> + constByteStrings [pk, sk, cipher] $ \ + [(ppk, _), (psk, _), (pc, _)] -> + c_box_seal_open pm pc (fromIntegral msgLen) ppk psk + hush . handleErrno err $ vec diff --git a/src/Crypto/Saltine/Core/Hash.hs b/src/Crypto/Saltine/Core/Hash.hs new file mode 100644 index 00000000..1bac2973 --- /dev/null +++ b/src/Crypto/Saltine/Core/Hash.hs @@ -0,0 +1,103 @@ + -- | +-- Module : Crypto.Saltine.Core.Hash +-- Copyright : (c) Joseph Abrahamson 2013 +-- License : MIT +-- +-- Maintainer : me@jspha.com +-- Stability : experimental +-- Portability : non-portable +-- +-- Hashing: "Crypto.Saltine.Core.Hash" +-- +-- The 'hash' function hashes a message 'ByteString' and returns a +-- hash. Hashes are always of length 'Bytes.hash'. The 'shorthash' +-- function hashes a message 'ByteString' with respect to a secret key +-- and returns a very short hash. Short hashes are always of length +-- 'Bytes.shorthash'. +-- +-- The 'hash' function is designed to be usable as a strong component +-- of DSA, RSA-PSS, key derivation, hash-based message-authentication +-- codes, hash-based ciphers, and various other common +-- applications. "Strong" means that the security of these +-- applications, when instantiated with 'hash', is the same as the +-- security of the applications against generic attacks. In +-- particular, the 'hash' function is designed to make finding +-- collisions difficult. +-- +-- 'hash' is currently an implementation of SHA-512. 'shorthash' is +-- currently an implementation of SipHash-2-4 +-- (). +-- +-- There has been considerable degradation of public confidence in the +-- security conjectures for many hash functions, including +-- SHA-512. However, for the moment, there do not appear to be +-- alternatives that inspire satisfactory levels of confidence. One +-- can hope that NIST's SHA-3 competition will improve the situation. +-- +-- Sodium includes an implementation of the Blake2b hash function +-- () and is bound here as the 'generichash' +-- function. +-- +-- This is version 2010.08.30 of the hash.html web page. Information +-- about SipHash has been added. +module Crypto.Saltine.Core.Hash ( + ShorthashKey, + hash, + shorthash, newShorthashKey, + GenerichashKey, + newGenerichashKey, + GenerichashOutLen, + generichashOutLen, generichash + ) where + +import Crypto.Saltine.Internal.Hash + ( c_hash + , c_generichash + , shorthash + , ShorthashKey(..) + , GenerichashKey(..) + , GenerichashOutLen(..) + ) +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) + +import qualified Crypto.Saltine.Internal.Hash as Bytes +import qualified Data.ByteString as S + +-- | Computes a cryptographically collision-resistant hash making +-- @hash m == hash m' ==> m == m'@ highly likely even when under +-- attack. +hash :: ByteString + -- ^ Message + -> ByteString + -- ^ Hash +hash m = snd . buildUnsafeByteString Bytes.hash_bytes $ \ph -> + constByteStrings [m] $ \[(pm, _)] -> c_hash ph pm (fromIntegral $ S.length m) + +-- | Randomly generates a new key for 'shorthash'. +newShorthashKey :: IO ShorthashKey +newShorthashKey = ShK <$> randomByteString Bytes.shorthash_keybytes + +-- | Randomly generates a new key for 'generichash' of the given length. +newGenerichashKey :: Int -> IO (Maybe GenerichashKey) +newGenerichashKey n = if n >= 0 && n <= Bytes.generichash_keybytes_max + then Just . GhK <$> randomByteString n + else return Nothing + +-- | Create a validated Generichash output length +generichashOutLen :: Int -> Maybe GenerichashOutLen +generichashOutLen n = if n > 0 && n <= Bytes.generichash_bytes_max + then Just $ GhOL $ fromIntegral n + else Nothing + +-- | Computes a generic, keyed hash. +generichash :: GenerichashKey + -> ByteString + -- ^ Message + -> GenerichashOutLen + -- ^ Desired output hash length + -> ByteString + -- ^ Hash +generichash (GhK k) m (GhOL outLen) = snd . buildUnsafeByteString outLen $ \ph -> + constByteStrings [k, m] $ \[(pk, _), (pm, _)] -> + c_generichash ph (fromIntegral outLen) pm (fromIntegral $ S.length m) pk (fromIntegral $ S.length k) diff --git a/src/Crypto/Saltine/Core/OneTimeAuth.hs b/src/Crypto/Saltine/Core/OneTimeAuth.hs new file mode 100644 index 00000000..ff6145a1 --- /dev/null +++ b/src/Crypto/Saltine/Core/OneTimeAuth.hs @@ -0,0 +1,80 @@ +-- | +-- Module : Crypto.Saltine.Core.OneTimeAuth +-- Copyright : (c) Joseph Abrahamson 2013 +-- License : MIT +-- +-- Maintainer : me@jspha.com +-- Stability : experimental +-- Portability : non-portable +-- +-- Secret-key single-message authentication: +-- "Crypto.Saltine.Core.OneTimeAuth" +-- +-- The 'auth' function authenticates a message 'ByteString' using a +-- secret key The function returns an authenticator. The 'verify' +-- function checks if it's passed a correct authenticator of a message +-- under the given secret key. +-- +-- The 'auth' function, viewed as a function of the message for a +-- uniform random key, is designed to meet the standard notion of +-- unforgeability after a single message. After the sender +-- authenticates one message, an attacker cannot find authenticators +-- for any other messages. +-- +-- The sender must not use 'auth' to authenticate more than one +-- message under the same key. Authenticators for two messages under +-- the same key should be expected to reveal enough information to +-- allow forgeries of authenticators on other messages. +-- +-- "Crypto.Saltine.Core.OneTimeAuth" is +-- @crypto_onetimeauth_poly1305@, an authenticator specified in +-- "Cryptography in NaCl" (), Section +-- 9. This authenticator is proven to meet the standard notion of +-- unforgeability after a single message. +-- +-- This is version 2010.08.30 of the onetimeauth.html web page. +module Crypto.Saltine.Core.OneTimeAuth ( + Key, Authenticator, + newKey, + auth, verify + ) where + +import Crypto.Saltine.Internal.OneTimeAuth + ( c_onetimeauth + , c_onetimeauth_verify + , Key(..) + , Authenticator(..) + ) +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) + +import qualified Crypto.Saltine.Internal.OneTimeAuth as Bytes +import qualified Data.ByteString as S + +-- | Creates a random key of the correct size for 'auth' and 'verify'. +newKey :: IO Key +newKey = Key <$> randomByteString Bytes.onetimeauth_keybytes + +-- | Builds a keyed 'Authenticator' for a message. This +-- 'Authenticator' is /impossible/ to forge so long as the 'Key' is +-- never used twice. +auth :: Key + -> ByteString + -- ^ Message + -> Authenticator +auth (Key key) msg = + Au . snd . buildUnsafeByteString Bytes.onetimeauth_bytes $ \pa -> + constByteStrings [key, msg] $ \[(pk, _), (pm, _)] -> + c_onetimeauth pa pm (fromIntegral $ S.length msg) pk + +-- | Verifies that an 'Authenticator' matches a given message and key. +verify :: Key + -> Authenticator + -> ByteString + -- ^ Message + -> Bool + -- ^ Is this message authentic? +verify (Key key) (Au a) msg = + unsafeDidSucceed $ constByteStrings [key, msg, a] $ \ + [(pk, _), (pm, _), (pa, _)] -> + return $ c_onetimeauth_verify pa pm (fromIntegral $ S.length msg) pk diff --git a/src/Crypto/Saltine/Core/Password.hs b/src/Crypto/Saltine/Core/Password.hs new file mode 100644 index 00000000..f3a39fca --- /dev/null +++ b/src/Crypto/Saltine/Core/Password.hs @@ -0,0 +1,247 @@ +-- | +-- Module : Crypto.Saltine.Core.Password +-- Description : Argon2 password hash +-- Copyright : (c) Promethea Raschke 2018 +-- Max Amanshauser 2021 +-- License : MIT +-- Maintainer : max@lambdalifting.org +-- Stability : experimental +-- Portability : non-portable +-- +-- Password hashing and key derivation +-- +-- When in doubt, just use one of [ interactivePolicy, moderatePolicy, sensitivePolicy ], +-- but this module also allows you to fine-tune parameters for specific circumstances. +-- +-- This module uses the @Text@ type for passwords, because this seems to be the only +-- reasonable way to get consistent encodings across locales and architectures, short of +-- letting users mess around with ByteStrings themselves. + +module Crypto.Saltine.Core.Password + ( Salt + , newSalt + + , needsRehash + + , pwhashStr + , pwhashStrVerify + , pwhash + + , Policy(..) + , interactivePolicy + , moderatePolicy + , sensitivePolicy + + , Opslimit + , opslimit + , getOpslimit + + , minOpslimit + , maxOpslimit + + , opslimitInteractive + , opslimitModerate + , opslimitSensitive + + , Memlimit + , memlimit + , getMemlimit + + , minMemlimit + , maxMemlimit + + , memlimitInteractive + , memlimitModerate + , memlimitSensitive + + , Algorithm + , defaultAlgorithm + ) where + +import Crypto.Saltine.Internal.Util +import Crypto.Saltine.Internal.Password as I +import Data.ByteString (ByteString) +import Data.Text (Text) +import Foreign.C +import System.IO.Unsafe + +import qualified Crypto.Saltine.Internal.Password as Bytes +import qualified Data.Text as T +import qualified Data.Text.Encoding as TE + + +newSalt :: IO Salt +newSalt = Salt <$> randomByteString Bytes.pwhash_saltbytes + +-- | Hashes a password according to the policy +-- This function is non-deterministic and hence in IO. +-- Since this function may cause a huge amount of memory to be allocated, it will return +-- Nothing if the allocation failed and on any other error. +pwhashStr :: Text -> Policy -> IO (Maybe PasswordHash) +pwhashStr pw policy = do + let (ops, mem, _alg) = unpackPolicy policy + + -- Hash is always ASCII, so no decoding needed + fmap (fmap (PasswordHash . T.pack)) $ allocaBytes pwhash_strbytes $ \pp -> + constByteStrings [TE.encodeUtf8 pw] $ \ [(ppw, ppwlen)] -> do + ret <- c_pwhash_str pp ppw (fromIntegral ppwlen) (fromIntegral ops) (fromIntegral mem) + + case ret of + 0 -> Just <$> peekCAString pp + _ -> pure Nothing + +-- | Verifies that a certain password hash was constructed from the supplied password +pwhashStrVerify :: PasswordHash -> Text -> Bool +pwhashStrVerify (PasswordHash h) pw = unsafePerformIO $ + constByteStrings [TE.encodeUtf8 $ T.snoc h '\NUL', TE.encodeUtf8 pw] $ \[(ph, _), (ppw, ppwlen)] -> do + res <- c_pwhash_str_verify ph ppw (fromIntegral ppwlen) + pure (res == 0) + +-- | Indicates whether a password needs to be rehashed, because the opslimit/memlimit parameters +-- used to hash the password are inconsistent with the supplied values. +-- Returns Nothing if the hash appears to be invalid. +-- Internally this function will always use the current DefaultAlgorithm and hence will give +-- undefined results if a different algorithm was used to hash the password. +needsRehash :: Opslimit -> Memlimit -> PasswordHash -> Maybe Bool +needsRehash (Opslimit ops) (Memlimit mem) (PasswordHash h) = + unsafePerformIO $ + constByteStrings [TE.encodeUtf8 $ T.snoc h '\NUL'] $ \[(ph,_)] -> do + res <- c_pwhash_str_needs_rehash ph (fromIntegral ops) (fromIntegral mem) + pure $ if res == -1 + then Nothing + else Just (res == 1) + +-- | Derives a key of the specified length from a password using a salt according to the provided policy. +-- Since this function may cause a huge amount of memory to be allocated, it will return +-- Nothing if the allocation failed and on any other error. +pwhash :: Text -> Int -> Salt -> Policy -> Maybe ByteString +pwhash pw len (Salt salt) policy = do + let (ops, mem, alg) = unpackPolicy policy + + let (e, hashed) = + buildUnsafeByteString len $ \hbuf -> + constByteStrings [TE.encodeUtf8 pw, salt] $ \[(ppw,ppwlen), (psalt,_)] -> + c_pwhash + hbuf (fromIntegral len) + ppw (fromIntegral ppwlen) + psalt + (fromIntegral ops) + (fromIntegral mem) + (fromIntegral $ fromEnum alg) + + if e == -1 + then Nothing + else Just hashed + + +-- | Smart constructor for opslimit +opslimit :: Algorithm -> Int -> Maybe Opslimit +opslimit alg x + | Opslimit x < minOpslimit alg = Nothing + | Opslimit x > maxOpslimit alg = Nothing + | otherwise = Just (Opslimit x) + +opslimitInteractive :: Algorithm -> Opslimit +opslimitInteractive DefaultAlgorithm = Opslimit (fromIntegral Bytes.pwhash_opslimit_interactive) +opslimitInteractive Argon2i13 = Opslimit (fromIntegral Bytes.pwhash_argon2i_opslimit_interactive) +opslimitInteractive Argon2id13 = Opslimit (fromIntegral Bytes.pwhash_argon2id_opslimit_interactive) + +opslimitModerate :: Algorithm -> Opslimit +opslimitModerate DefaultAlgorithm = Opslimit (fromIntegral Bytes.pwhash_opslimit_moderate) +opslimitModerate Argon2i13 = Opslimit (fromIntegral Bytes.pwhash_argon2i_opslimit_moderate) +opslimitModerate Argon2id13 = Opslimit (fromIntegral Bytes.pwhash_argon2id_opslimit_moderate) + +opslimitSensitive :: Algorithm -> Opslimit +opslimitSensitive DefaultAlgorithm = Opslimit (fromIntegral Bytes.pwhash_opslimit_sensitive) +opslimitSensitive Argon2i13 = Opslimit (fromIntegral Bytes.pwhash_argon2i_opslimit_sensitive) +opslimitSensitive Argon2id13 = Opslimit (fromIntegral Bytes.pwhash_argon2id_opslimit_sensitive) + + +-- | Smart constructor for memlimit +memlimit :: Algorithm -> Int -> Maybe Memlimit +memlimit alg x + | Memlimit x < minMemlimit alg = Nothing + | Memlimit x > maxMemlimit alg= Nothing + | otherwise = Just (Memlimit x) + +memlimitInteractive :: Algorithm -> Memlimit +memlimitInteractive DefaultAlgorithm = Memlimit (fromIntegral Bytes.pwhash_memlimit_interactive) +memlimitInteractive Argon2i13 = Memlimit (fromIntegral Bytes.pwhash_argon2i_memlimit_interactive) +memlimitInteractive Argon2id13 = Memlimit (fromIntegral Bytes.pwhash_argon2id_memlimit_interactive) + +memlimitModerate :: Algorithm -> Memlimit +memlimitModerate DefaultAlgorithm = Memlimit (fromIntegral Bytes.pwhash_memlimit_moderate) +memlimitModerate Argon2i13 = Memlimit (fromIntegral Bytes.pwhash_argon2i_memlimit_moderate) +memlimitModerate Argon2id13 = Memlimit (fromIntegral Bytes.pwhash_argon2id_memlimit_moderate) + +memlimitSensitive :: Algorithm -> Memlimit +memlimitSensitive DefaultAlgorithm = Memlimit (fromIntegral Bytes.pwhash_memlimit_sensitive) +memlimitSensitive Argon2i13 = Memlimit (fromIntegral Bytes.pwhash_argon2i_memlimit_sensitive) +memlimitSensitive Argon2id13 = Memlimit (fromIntegral Bytes.pwhash_argon2id_memlimit_sensitive) + +defaultAlgorithm :: Algorithm +defaultAlgorithm = DefaultAlgorithm + + +-- | Get raw C types from a policy, suitable for passing to FFI functions +unpackPolicy :: Policy -> (CULLong, CSize, CInt) +unpackPolicy (Policy ops mem alg) = + ( fromIntegral (getOpslimit ops) + , fromIntegral (getMemlimit mem) + , algorithm alg + ) + + +{- +Fast policy suitable for low-powered devices + +Takes approximately 0.1 seconds on a typical desktop computer +and requires 64 MiB of dedicated RAM +-} +interactivePolicy :: Policy +interactivePolicy = Policy (opslimitInteractive defaultAlgorithm) + (memlimitInteractive defaultAlgorithm) + defaultAlgorithm + +{-| +Moderate policy with a balance of speed and security + +Takes approximately 1 second on a typical desktop computer +and requires 256 MiB of dedicated RAM +-} +moderatePolicy :: Policy +moderatePolicy = Policy (opslimitModerate defaultAlgorithm) + (memlimitModerate defaultAlgorithm) + defaultAlgorithm + +{-| +High-security policy designed to make attacking the password extremely expensive + +Takes several seconds on a typical desktop computer +and requires 1024 MiB of dedicated RAM +-} +sensitivePolicy :: Policy +sensitivePolicy = Policy (opslimitSensitive defaultAlgorithm) + (memlimitSensitive defaultAlgorithm) + defaultAlgorithm + + +minOpslimit :: Algorithm -> Opslimit +minOpslimit DefaultAlgorithm = Opslimit $ fromIntegral Bytes.pwhash_opslimit_min +minOpslimit Argon2i13 = Opslimit $ fromIntegral Bytes.pwhash_argon2i_opslimit_min +minOpslimit Argon2id13 = Opslimit $ fromIntegral Bytes.pwhash_argon2id_opslimit_min + +maxOpslimit :: Algorithm -> Opslimit +maxOpslimit DefaultAlgorithm = Opslimit $ fromIntegral Bytes.pwhash_opslimit_max +maxOpslimit Argon2i13 = Opslimit $ fromIntegral Bytes.pwhash_argon2i_opslimit_max +maxOpslimit Argon2id13 = Opslimit $ fromIntegral Bytes.pwhash_argon2id_opslimit_max + +minMemlimit :: Algorithm -> Memlimit +minMemlimit DefaultAlgorithm = Memlimit $ fromIntegral Bytes.pwhash_memlimit_min +minMemlimit Argon2i13 = Memlimit $ fromIntegral Bytes.pwhash_argon2i_memlimit_min +minMemlimit Argon2id13 = Memlimit $ fromIntegral Bytes.pwhash_argon2id_memlimit_min + +maxMemlimit :: Algorithm -> Memlimit +maxMemlimit DefaultAlgorithm = Memlimit $ fromIntegral Bytes.pwhash_memlimit_max +maxMemlimit Argon2i13 = Memlimit $ fromIntegral Bytes.pwhash_argon2i_memlimit_max +maxMemlimit Argon2id13 = Memlimit $ fromIntegral Bytes.pwhash_argon2id_memlimit_max diff --git a/src/Crypto/Saltine/Core/ScalarMult.hs b/src/Crypto/Saltine/Core/ScalarMult.hs new file mode 100644 index 00000000..9380754b --- /dev/null +++ b/src/Crypto/Saltine/Core/ScalarMult.hs @@ -0,0 +1,79 @@ +{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving, DeriveGeneric #-} + +-- | +-- Module : Crypto.Saltine.Core.ScalarMult +-- Copyright : (c) Joseph Abrahamson 2013 +-- License : MIT +-- +-- Maintainer : me@jspha.com +-- Stability : experimental +-- Portability : non-portable +-- +-- Scalar multiplication: "Crypto.Saltine.Core.ScalarMult" +-- +-- The 'mult' function multiplies a group element by an integer of +-- length 'Bytes.multScalar'. It returns the resulting group element +-- of length 'Bytes.mult'. The 'multBase' function multiplies a +-- standard group element by an integer of length +-- 'Bytes.multScalar'. It returns the resulting group element of +-- length 'Bytes.mult'. +-- +-- The correspondence between strings and group elements depends on +-- the primitive implemented by 'mult'. The correspondence is not +-- necessarily injective in either direction, but it is compatible +-- with scalar multiplication in the group. The correspondence does +-- not necessarily include all group elements, but it does include all +-- strings; i.e., every string represents at least one group element. +-- +-- The correspondence between strings and integers also depends on the +-- primitive implemented by 'mult'. Every string represents at least +-- one integer. +-- +-- 'mult' is designed to be strong as a component of various +-- well-known \"hashed Diffie–Hellman\" applications. In particular, +-- it is designed to make the \"computational Diffie–Hellman\" problem +-- (CDH) difficult with respect to the standard base. 'mult' is also +-- designed to make CDH difficult with respect to other nontrivial +-- bases. In particular, if a represented group element has small +-- order, then it is annihilated by all represented scalars. This +-- feature allows protocols to avoid validating membership in the +-- subgroup generated by the standard base. +-- +-- NaCl does not make any promises regarding the \"decisional +-- Diffie–Hellman\" problem (DDH), the \"static Diffie–Hellman\" +-- problem (SDH), etc. Users are responsible for hashing group +-- elements. +-- +-- 'mult' is the function @crypto_scalarmult_curve25519@ specified in +-- \"Cryptography in NaCl\", Sections 2, 3, and 4 +-- (). This function is conjectured +-- to be strong. For background see Bernstein, \"Curve25519: new +-- Diffie-Hellman speed records,\" Lecture Notes in Computer Science +-- 3958 (2006), 207–228, . +-- +-- This is version 2010.08.30 of the scalarmult.html web page. +module Crypto.Saltine.Core.ScalarMult ( + Scalar, GroupElement, + mult, multBase + ) where + +import Crypto.Saltine.Internal.Util +import Crypto.Saltine.Internal.ScalarMult + ( c_scalarmult + , c_scalarmult_base + , GroupElement(..) + , Scalar(..) + ) + +import qualified Crypto.Saltine.Internal.ScalarMult as Bytes + + +mult :: Scalar -> GroupElement -> GroupElement +mult (Sc n) (GE p) = GE . snd . buildUnsafeByteString Bytes.scalarmult_bytes $ \pq -> + constByteStrings [n, p] $ \[(pn, _), (pp, _)] -> + c_scalarmult pq pn pp + +multBase :: Scalar -> GroupElement +multBase (Sc n) = GE . snd . buildUnsafeByteString Bytes.scalarmult_bytes $ \pq -> + constByteStrings [n] $ \[(pn, _)] -> + c_scalarmult_base pq pn diff --git a/src/Crypto/Saltine/Core/SecretBox.hs b/src/Crypto/Saltine/Core/SecretBox.hs new file mode 100644 index 00000000..1d80ef17 --- /dev/null +++ b/src/Crypto/Saltine/Core/SecretBox.hs @@ -0,0 +1,146 @@ +-- | +-- Module : Crypto.Saltine.Core.SecretBox +-- Copyright : (c) Joseph Abrahamson 2013 +-- License : MIT +-- +-- Maintainer : max@lambdalifting.org +-- Stability : experimental +-- Portability : non-portable +-- +-- Secret-key authenticated encryption: +-- "Crypto.Saltine.Core.SecretBox" +-- +-- The 'secretbox' function encrypts and authenticates a message +-- 'ByteString' using a secret key and a nonce. The 'secretboxOpen' +-- function verifies and decrypts a ciphertext 'ByteString' using a +-- secret key and a nonce. If the ciphertext fails validation, +-- 'secretboxOpen' returns 'Nothing'. +-- +-- The "Crypto.Saltine.Core.SecretBox" module is designed to meet +-- the standard notions of privacy and authenticity for a secret-key +-- authenticated-encryption scheme using nonces. For formal +-- definitions see, e.g., Bellare and Namprempre, "Authenticated +-- encryption: relations among notions and analysis of the generic +-- composition paradigm," Lecture Notes in Computer Science 1976 +-- (2000), 531–545, . +-- +-- Note that the length is not hidden. Note also that it is the +-- caller's responsibility to ensure the uniqueness of nonces—for +-- example, by using nonce 1 for the first message, nonce 2 for the +-- second message, etc. Nonces are long enough that randomly generated +-- nonces have negligible risk of collision. +-- +-- "Crypto.Saltine.Core.SecretBox" is +-- @crypto_secretbox_xsalsa20poly1305@, a particular combination of +-- Salsa20 and Poly1305 specified in \"Cryptography in NaCl\" +-- (). This function is conjectured +-- to meet the standard notions of privacy and authenticity. +-- +-- This is version 2010.08.30 of the secretbox.html web page. +module Crypto.Saltine.Core.SecretBox ( + Key, Nonce, Authenticator, + secretbox, secretboxOpen, + secretboxDetached, secretboxOpenDetached, + newKey, newNonce + ) where + +import Crypto.Saltine.Internal.SecretBox + ( c_secretbox + , c_secretbox_detached + , c_secretbox_open + , c_secretbox_open_detached + , Key(..) + , Nonce(..) + , Authenticator(..) + ) +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) + +import qualified Crypto.Saltine.Internal.SecretBox as Bytes +import qualified Data.ByteString as S + +-- | Creates a random key of the correct size for 'secretbox'. +newKey :: IO Key +newKey = Key <$> randomByteString Bytes.secretbox_keybytes + +-- | Creates a random nonce of the correct size for 'secretbox'. +newNonce :: IO Nonce +newNonce = Nonce <$> randomByteString Bytes.secretbox_noncebytes + +-- | Encrypts a message. It is infeasible for an attacker to decrypt +-- the message so long as the 'Nonce' is never repeated. +secretbox + :: Key + -> Nonce + -> ByteString + -- ^ Message + -> ByteString + -- ^ Ciphertext +secretbox (Key key) (Nonce nonce) msg = + unpad' . snd . buildUnsafeByteString len $ \pc -> + constByteStrings [key, pad' msg, nonce] $ \ + [(pk, _), (pm, _), (pn, _)] -> + c_secretbox pc pm (fromIntegral len) pn pk + where len = S.length msg + Bytes.secretbox_zerobytes + pad' = pad Bytes.secretbox_zerobytes + unpad' = unpad Bytes.secretbox_boxzerobytes + +-- | Encrypts a message. In contrast with 'secretbox', the result is not +-- serialized as one element and instead provided as an authentication tag and +-- ciphertext. +secretboxDetached + :: Key + -> Nonce + -> ByteString + -- ^ Message + -> (Authenticator,ByteString) + -- ^ (Authentication Tag, Ciphertext) +secretboxDetached (Key key) (Nonce nonce) msg = + buildUnsafeByteString ctLen $ \pc -> + fmap (Au . snd) . buildUnsafeByteString' tagLen $ \ptag -> + constByteStrings [key, msg, nonce] $ \ + [(pk, _), (pmsg, _), (pn, _)] -> + c_secretbox_detached pc ptag pmsg (fromIntegral ptLen) pn pk + where ctLen = ptLen + ptLen = S.length msg + tagLen = Bytes.secretbox_macbytes + +-- | Decrypts a message. Returns 'Nothing' if the keys and message do +-- not match. +secretboxOpen + :: Key + -> Nonce + -> ByteString + -- ^ Ciphertext + -> Maybe ByteString + -- ^ Message +secretboxOpen (Key key) (Nonce nonce) cipher = + let (err, vec) = buildUnsafeByteString len $ \pm -> + constByteStrings [key, pad' cipher, nonce] $ \ + [(pk, _), (pc, _), (pn, _)] -> + c_secretbox_open pm pc (fromIntegral len) pn pk + in hush . handleErrno err $ unpad' vec + where len = S.length cipher + Bytes.secretbox_boxzerobytes + pad' = pad Bytes.secretbox_boxzerobytes + unpad' = unpad Bytes.secretbox_zerobytes + +-- | Decrypts a message. Returns 'Nothing' if the keys and message do +-- not match. +secretboxOpenDetached + :: Key + -> Nonce + -> Authenticator + -- ^ Auth Tag + -> ByteString + -- ^ Ciphertext + -> Maybe ByteString + -- ^ Message +secretboxOpenDetached (Key key) (Nonce nonce) (Au tag) cipher + | S.length tag /= Bytes.secretbox_macbytes = Nothing + | otherwise = + let (err, vec) = buildUnsafeByteString len $ \pm -> + constByteStrings [key, cipher, tag, nonce] $ \ + [(pk, _), (pc, _), (pt, _), (pn, _)] -> + c_secretbox_open_detached pm pc pt (fromIntegral len) pn pk + in hush . handleErrno err $ vec + where len = S.length cipher diff --git a/src/Crypto/Saltine/Core/Sign.hs b/src/Crypto/Saltine/Core/Sign.hs new file mode 100644 index 00000000..40d3ed9c --- /dev/null +++ b/src/Crypto/Saltine/Core/Sign.hs @@ -0,0 +1,131 @@ + +-- | +-- Module : Crypto.Saltine.Core.Sign +-- Copyright : (c) Joseph Abrahamson 2013 +-- License : MIT +-- +-- Maintainer : me@jspha.com +-- Stability : experimental +-- Portability : non-portable +-- +-- Signatures: "Crypto.Saltine.Core.Sign" +-- +-- The 'newKeypair' function randomly generates a secret key and a +-- corresponding public key. The 'sign' function signs a message +-- 'ByteString' using the signer's secret key and returns the +-- resulting signed message. The 'signOpen' function verifies the +-- signature in a signed message using the signer's public key then +-- returns the message without its signature. +-- +-- "Crypto.Saltine.Core.Sign" is an EdDSA signature using +-- elliptic-curve Curve25519 (see: ). See +-- also, \"Daniel J. Bernstein, Niels Duif, Tanja Lange, Peter +-- Schwabe, Bo-Yin Yang. High-speed high-security signatures. Journal +-- of Cryptographic Engineering 2 (2012), 77–89.\" +-- . +-- +-- This is current information as of 2013 June 6. + +module Crypto.Saltine.Core.Sign ( + SecretKey, PublicKey, Keypair(..), Signature, + newKeypair, + sign, signOpen, + signDetached, signVerifyDetached + ) where + +import Crypto.Saltine.Internal.Sign + ( c_sign_keypair + , c_sign + , c_sign_open + , c_sign_detached + , c_sign_verify_detached + , SecretKey(..) + , PublicKey(..) + , Keypair(..) + , Signature(..) + ) +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) +import Foreign.Marshal.Alloc +import Foreign.Storable +import System.IO.Unsafe + +import qualified Crypto.Saltine.Internal.Sign as Bytes +import qualified Data.ByteString as S + +-- | Creates a random key of the correct size for 'sign' and +-- 'signOpen' of form @(secretKey, publicKey)@. +newKeypair :: IO Keypair +newKeypair = do + -- This is a little bizarre and a likely source of errors. + -- _err ought to always be 0. + ((_err, sk), pk) <- buildUnsafeByteString' Bytes.sign_publickeybytes $ \pkbuf -> + buildUnsafeByteString' Bytes.sign_secretkeybytes $ \skbuf -> + c_sign_keypair pkbuf skbuf + return $ Keypair (SK sk) (PK pk) + +-- | Augments a message with a signature forming a \"signed +-- message\". +sign :: SecretKey + -> ByteString + -- ^ Message + -> ByteString + -- ^ Signed message +sign (SK k) m = unsafePerformIO $ + alloca $ \psmlen -> do + (_err, sm) <- buildUnsafeByteString' (len + Bytes.sign_bytes) $ \psmbuf -> + constByteStrings [k, m] $ \[(pk, _), (pm, _)] -> + c_sign psmbuf psmlen pm (fromIntegral len) pk + smlen <- peek psmlen + return $ S.take (fromIntegral smlen) sm + where len = S.length m + +-- | Checks a \"signed message\" returning 'Just' the original message +-- iff the signature was generated using the 'SecretKey' corresponding +-- to the given 'PublicKey'. Returns 'Nothing' otherwise. +signOpen :: PublicKey + -> ByteString + -- ^ Signed message + -> Maybe ByteString + -- ^ Maybe the restored message +signOpen (PK k) sm = unsafePerformIO $ + alloca $ \pmlen -> do + (err, m) <- buildUnsafeByteString' smlen $ \pmbuf -> + constByteStrings [k, sm] $ \[(pk, _), (psm, _)] -> + c_sign_open pmbuf pmlen psm (fromIntegral smlen) pk + mlen <- peek pmlen + case err of + 0 -> return $ Just $ S.take (fromIntegral mlen) m + _ -> return Nothing + where smlen = S.length sm + +-- | Returns just the signature for a message using a SecretKey. +signDetached :: SecretKey + -> ByteString + -- ^ Message + -> Signature + -- ^ Signature +signDetached (SK k) m = unsafePerformIO $ + alloca $ \psmlen -> do + (_err, sm) <- buildUnsafeByteString' Bytes.sign_bytes $ \sigbuf -> + constByteStrings [k, m] $ \[(pk, _), (pm, _)] -> + c_sign_detached sigbuf psmlen pm (fromIntegral len) pk + smlen <- peek psmlen + return $ Signature $ S.take (fromIntegral smlen) sm + where len = S.length m + +-- | Returns @True@ if the signature is valid for the given public key and +-- message. +signVerifyDetached :: PublicKey + -> Signature + -- ^ Signature + -> ByteString + -- ^ Message (not signed) + -> Bool +signVerifyDetached (PK k) (Signature sig) sm = unsafePerformIO $ + constByteStrings [k, sig, sm] $ \[(pk, _), (psig, _), (psm, _)] -> do + res <- c_sign_verify_detached psig psm (fromIntegral len) pk + return (res == 0) + where len = S.length sm + + diff --git a/src/Crypto/Saltine/Core/Stream.hs b/src/Crypto/Saltine/Core/Stream.hs new file mode 100644 index 00000000..78b36ffb --- /dev/null +++ b/src/Crypto/Saltine/Core/Stream.hs @@ -0,0 +1,103 @@ +-- | +-- Module : Crypto.Saltine.Core.Stream +-- Copyright : (c) Joseph Abrahamson 2013 +-- License : MIT +-- +-- Maintainer : me@jspha.com +-- Stability : experimental +-- Portability : non-portable +-- +-- Secret-key encryption: +-- "Crypto.Saltine.Core.Stream" +-- +-- The 'stream' function produces a sized stream 'ByteString' as a +-- function of a secret key and a nonce. The 'xor' function encrypts a +-- message 'ByteString' using a secret key and a nonce. The 'xor' +-- function guarantees that the ciphertext has the same length as the +-- plaintext, and is the @plaintext `xor` stream k n@. Consequently +-- 'xor' can also be used to decrypt. +-- +-- The 'stream' function, viewed as a function of the nonce for a +-- uniform random key, is designed to meet the standard notion of +-- unpredictability (\"PRF\"). For a formal definition see, e.g., +-- Section 2.3 of Bellare, Kilian, and Rogaway, \"The security of the +-- cipher block chaining message authentication code,\" Journal of +-- Computer and System Sciences 61 (2000), 362–399; +-- . This means that +-- an attacker cannot distinguish this function from a uniform random +-- function. Consequently, if a series of messages is encrypted by +-- 'xor' with /a different nonce for each message/, the ciphertexts +-- are indistinguishable from uniform random strings of the same +-- length. +-- +-- Note that the length is not hidden. Note also that it is the +-- caller's responsibility to ensure the uniqueness of nonces—for +-- example, by using nonce 1 for the first message, nonce 2 for the +-- second message, etc. Nonces are long enough that randomly generated +-- nonces have negligible risk of collision. +-- +-- Saltine does not make any promises regarding the resistance of +-- crypto_stream to \"related-key attacks.\" It is the caller's +-- responsibility to use proper key-derivation functions. +-- +-- "Crypto.Saltine.Core.Stream" is @crypto_stream_xsalsa20@, a +-- particular cipher specified in \"Cryptography in NaCl\" +-- (), Section 7. This cipher is +-- conjectured to meet the standard notion of unpredictability. +-- +-- This is version 2010.08.30 of the stream.html web page. + +module Crypto.Saltine.Core.Stream ( + Key, Nonce, + newKey, newNonce, + stream, xor + ) where + +import Crypto.Saltine.Internal.Stream ( c_stream + , c_stream_xor + , Key(..) + , Nonce(..) + ) +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) + +import qualified Crypto.Saltine.Internal.Stream as Bytes +import qualified Data.ByteString as S + +-- | Creates a random key of the correct size for 'stream' and 'xor'. +newKey :: IO Key +newKey = Key <$> randomByteString Bytes.stream_keybytes + +-- | Creates a random nonce of the correct size for 'stream' and +-- 'xor'. +newNonce :: IO Nonce +newNonce = Nonce <$> randomByteString Bytes.stream_noncebytes + +-- | Generates a cryptographic random stream indexed by the 'Key' and +-- 'Nonce'. These streams are indistinguishable from random noise so +-- long as the 'Nonce' is not used more than once. +stream :: Key -> Nonce -> Int + -> ByteString + -- ^ Cryptographic stream +stream (Key key) (Nonce nonce) n = + snd . buildUnsafeByteString n $ \ps -> + constByteStrings [key, nonce] $ \[(pk, _), (pn, _)] -> + c_stream ps (fromIntegral n) pn pk + +-- | Computes the exclusive-or between a message and a cryptographic +-- random stream indexed by the 'Key' and the 'Nonce'. This renders +-- the output indistinguishable from random noise so long as the +-- 'Nonce' is not used more than once. /Note:/ while this can be used +-- for encryption and decryption, it is /possible for an attacker to/ +-- /manipulate the message in transit without detection/. USE AT YOUR +-- OWN RISK. +xor :: Key -> Nonce + -> ByteString + -- ^ Message + -> ByteString + -- ^ Ciphertext +xor (Key key) (Nonce nonce) msg = + snd . buildUnsafeByteString len $ \pc -> + constByteStrings [key, nonce, msg] $ \[(pk, _), (pn, _), (pm, _)] -> + c_stream_xor pc pm (fromIntegral len) pn pk + where len = S.length msg diff --git a/src/Crypto/Saltine/Core/Utils.hs b/src/Crypto/Saltine/Core/Utils.hs new file mode 100644 index 00000000..77d97aeb --- /dev/null +++ b/src/Crypto/Saltine/Core/Utils.hs @@ -0,0 +1,6 @@ +module Crypto.Saltine.Core.Utils + ( Crypto.Saltine.Internal.Util.randomByteString + , Crypto.Saltine.Internal.Util.bin2hex + ) where + +import qualified Crypto.Saltine.Internal.Util diff --git a/src/Crypto/Saltine/Internal/AEAD/AES256GCM.hs b/src/Crypto/Saltine/Internal/AEAD/AES256GCM.hs new file mode 100644 index 00000000..199c0210 --- /dev/null +++ b/src/Crypto/Saltine/Internal/AEAD/AES256GCM.hs @@ -0,0 +1,188 @@ +{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving, DeriveGeneric, ForeignFunctionInterface #-} +-- | +-- Module : Crypto.Saltine.Internal.AEAD.AES256GCM +-- Copyright : (c) Max Amanshauser 2021 +-- License : MIT +-- +-- Maintainer : max@lambdalifting.org +-- Stability : experimental +-- Portability : non-portable +-- +module Crypto.Saltine.Internal.AEAD.AES256GCM ( + aead_aes256gcm_keybytes + , aead_aes256gcm_npubbytes + , aead_aes256gcm_abytes + , c_aead_aes256gcm_is_available + , c_aead + , c_aead_open + , c_aead_detached + , c_aead_open_detached + , Key(..) + , Nonce(..) +) where + +import Control.DeepSeq +import Crypto.Saltine.Class +import Crypto.Saltine.Core.Hash (shorthash) +import Crypto.Saltine.Internal.Hash (nullShKey) +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) +import Data.Data (Data, Typeable) +import Data.Hashable (Hashable) +import Data.Monoid +import GHC.Generics (Generic) +import Foreign.C +import Foreign.Ptr + +import qualified Data.ByteString as S + +-- | An opaque 'AES256GCM' cryptographic key. +newtype Key = Key { unKey :: ByteString } deriving (Ord, Hashable, Data, Typeable, Generic, NFData) +instance Eq Key where + Key a == Key b = U.compare a b +instance Show Key where + show k = "AEAD.AES256GCM.Key {hashesTo = \"" <> (bin2hex . shorthash nullShKey $ encode k) <> "\"}" + +instance IsEncoding Key where + decode v = if S.length v == aead_aes256gcm_keybytes + then Just (Key v) + else Nothing + {-# INLINE decode #-} + encode (Key v) = v + {-# INLINE encode #-} + +-- | An opaque 'AES256GCM' nonce. +newtype Nonce = Nonce { unNonce :: ByteString } deriving (Eq, Ord, Hashable, Data, Typeable, Generic, NFData) +instance Show Nonce where + show k = "AEAD.AES256GCM.Nonce " <> bin2hex (encode k) + +instance IsEncoding Nonce where + decode v = if S.length v == aead_aes256gcm_npubbytes + then Just (Nonce v) + else Nothing + {-# INLINE decode #-} + encode (Nonce v) = v + {-# INLINE encode #-} + +instance IsNonce Nonce where + zero = Nonce (S.replicate aead_aes256gcm_npubbytes 0) + nudge (Nonce n) = Nonce (nudgeBS n) + + +aead_aes256gcm_keybytes, aead_aes256gcm_abytes, aead_aes256gcm_npubbytes :: Int + +-- | Size of an AES256 key +aead_aes256gcm_keybytes = fromIntegral c_crypto_aead_aes256gcm_keybytes +-- | Size of an AES256 nonce +aead_aes256gcm_npubbytes = fromIntegral c_crypto_aead_aes256gcm_npubbytes +-- | Size of an AES256 authentication tag +aead_aes256gcm_abytes = fromIntegral c_crypto_aead_aes256gcm_abytes + + +-- src/libsodium/crypto_aead/aes256gcm/sodium/aead_aes256gcm.c +-- src/libsodium/include/sodium/crypto_aead_aes256gcm.h +foreign import ccall "crypto_aead_aes256gcm_keybytes" + c_crypto_aead_aes256gcm_keybytes :: CSize +foreign import ccall "crypto_aead_aes256gcm_npubbytes" + c_crypto_aead_aes256gcm_npubbytes:: CSize +foreign import ccall "crypto_aead_aes256gcm_abytes" + c_crypto_aead_aes256gcm_abytes :: CSize + + +foreign import ccall "crypto_aead_aes256gcm_is_available" + c_aead_aes256gcm_is_available + :: IO CInt + +-- | The aead C API uses C strings. Always returns 0. +foreign import ccall "crypto_aead_aes256gcm_encrypt" + c_aead + :: Ptr CChar + -- ^ Cipher output buffer + -> Ptr CULLong + -- ^ Cipher output bytes used + -> Ptr CChar + -- ^ Constant message input buffer + -> CULLong + -- ^ Length of message input buffer + -> Ptr CChar + -- ^ Constant aad input buffer + -> CULLong + -- ^ Length of aad input buffer + -> Ptr CChar + -- ^ Unused 'nsec' value (must be NULL) + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt + +-- | The aead open C API uses C strings. Returns 0 if successful. +foreign import ccall "crypto_aead_aes256gcm_decrypt" + c_aead_open + :: Ptr CChar + -- ^ Message output buffer + -> Ptr CULLong + -- ^ Message output bytes used + -> Ptr CChar + -- ^ Unused 'nsec' value (must be NULL) + -> Ptr CChar + -- ^ Constant ciphertext input buffer + -> CULLong + -- ^ Length of ciphertext input buffer + -> Ptr CChar + -- ^ Constant aad input buffer + -> CULLong + -- ^ Length of aad input buffer + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt + +-- | The aead C API uses C strings. Always returns 0. +foreign import ccall "crypto_aead_aes256gcm_encrypt_detached" + c_aead_detached + :: Ptr CChar + -- ^ Cipher output buffer + -> Ptr CChar + -- ^ Tag output buffer + -> Ptr CULLong + -- ^ Tag bytes used + -> Ptr CChar + -- ^ Constant message input buffer + -> CULLong + -- ^ Length of message input buffer + -> Ptr CChar + -- ^ Constant aad input buffer + -> CULLong + -- ^ Length of aad input buffer + -> Ptr CChar + -- ^ Unused 'nsec' value (must be NULL) + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt + +-- | The aead open C API uses C strings. Returns 0 if successful. +foreign import ccall "crypto_aead_aes256gcm_decrypt_detached" + c_aead_open_detached + :: Ptr CChar + -- ^ Message output buffer + -> Ptr CChar + -- ^ Unused 'nsec' value (must be NULL) + -> Ptr CChar + -- ^ Constant ciphertext input buffer + -> CULLong + -- ^ Length of ciphertext input buffer + -> Ptr CChar + -- ^ Constant tag input buffer + -> Ptr CChar + -- ^ Constant aad input buffer + -> CULLong + -- ^ Length of aad input buffer + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt diff --git a/src/Crypto/Saltine/Internal/AEAD/ChaCha20Poly1305.hs b/src/Crypto/Saltine/Internal/AEAD/ChaCha20Poly1305.hs new file mode 100644 index 00000000..57081864 --- /dev/null +++ b/src/Crypto/Saltine/Internal/AEAD/ChaCha20Poly1305.hs @@ -0,0 +1,184 @@ +{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving, DeriveGeneric, ForeignFunctionInterface #-} +-- | +-- Module : Crypto.Saltine.Internal.AEAD.ChaCha20Poly1305 +-- Copyright : (c) Max Amanshauser 2021 +-- License : MIT +-- +-- Maintainer : max@lambdalifting.org +-- Stability : experimental +-- Portability : non-portable +-- +module Crypto.Saltine.Internal.AEAD.ChaCha20Poly1305 ( + aead_chacha20poly1305_keybytes + , aead_chacha20poly1305_npubbytes + , aead_chacha20poly1305_abytes + , c_aead + , c_aead_open + , c_aead_detached + , c_aead_open_detached + , Key(..) + , Nonce(..) +) where + +import Control.DeepSeq +import Crypto.Saltine.Class +import Crypto.Saltine.Core.Hash (shorthash) +import Crypto.Saltine.Internal.Hash (nullShKey) +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) +import Data.Data (Data, Typeable) +import Data.Hashable (Hashable) +import Data.Monoid +import Foreign.C +import Foreign.Ptr +import GHC.Generics (Generic) + +import qualified Data.ByteString as S + + +-- | An opaque 'ChaCha20Poly1305' cryptographic key. +newtype Key = Key { unKey :: ByteString } deriving (Ord, Hashable, Data, Typeable, Generic, NFData) +instance Eq Key where + Key a == Key b = U.compare a b +instance Show Key where + show k = "AEAD.ChaCha20Poly1305.Key {hashesTo = \"" <> (bin2hex . shorthash nullShKey $ encode k) <> "\"}" + +instance IsEncoding Key where + decode v = if S.length v == aead_chacha20poly1305_keybytes + then Just (Key v) + else Nothing + {-# INLINE decode #-} + encode (Key v) = v + {-# INLINE encode #-} + +-- | An opaque 'ChaCha20Poly1305' nonce. +newtype Nonce = Nonce { unNonce :: ByteString } deriving (Eq, Ord, Hashable, Data, Typeable, Generic, NFData) +instance Show Nonce where + show k = "AEAD.ChaCha20Poly1305.Nonce " <> bin2hex (encode k) + +instance IsEncoding Nonce where + decode v = if S.length v == aead_chacha20poly1305_npubbytes + then Just (Nonce v) + else Nothing + {-# INLINE decode #-} + encode (Nonce v) = v + {-# INLINE encode #-} + +instance IsNonce Nonce where + zero = Nonce (S.replicate aead_chacha20poly1305_npubbytes 0) + nudge (Nonce n) = Nonce (nudgeBS n) + + +aead_chacha20poly1305_keybytes, aead_chacha20poly1305_abytes, aead_chacha20poly1305_npubbytes :: Int + +-- | Size of a ChaCha20-Poly1305 key +aead_chacha20poly1305_keybytes = fromIntegral c_crypto_aead_chacha20poly1305_keybytes +-- | Size of a ChaCha20-Poly1305 nonce +aead_chacha20poly1305_npubbytes = fromIntegral c_crypto_aead_chacha20poly1305_npubbytes +-- | Size of a ChaCha20-Poly1305 authentication tag +aead_chacha20poly1305_abytes = fromIntegral c_crypto_aead_chacha20poly1305_abytes + + +-- src/libsodium/crypto_aead/xchacha20poly1305/sodium/aead_xchacha20poly1305.c +-- src/libsodium/include/sodium/crypto_aead_xchacha20poly1305.h +foreign import ccall "crypto_aead_chacha20poly1305_keybytes" + c_crypto_aead_chacha20poly1305_keybytes :: CSize +foreign import ccall "crypto_aead_chacha20poly1305_npubbytes" + c_crypto_aead_chacha20poly1305_npubbytes:: CSize +foreign import ccall "crypto_aead_chacha20poly1305_abytes" + c_crypto_aead_chacha20poly1305_abytes :: CSize + + +-- | The aead C API uses C strings. Always returns 0. +foreign import ccall "crypto_aead_chacha20poly1305_encrypt" + c_aead + :: Ptr CChar + -- ^ Cipher output buffer + -> Ptr CULLong + -- ^ Cipher output bytes used + -> Ptr CChar + -- ^ Constant message input buffer + -> CULLong + -- ^ Length of message input buffer + -> Ptr CChar + -- ^ Constant aad input buffer + -> CULLong + -- ^ Length of aad input buffer + -> Ptr CChar + -- ^ Unused 'nsec' value (must be NULL) + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt + +-- | The aead open C API uses C strings. Returns 0 if successful. +foreign import ccall "crypto_aead_chacha20poly1305_decrypt" + c_aead_open + :: Ptr CChar + -- ^ Message output buffer + -> Ptr CULLong + -- ^ Message output bytes used + -> Ptr CChar + -- ^ Unused 'nsec' value (must be NULL) + -> Ptr CChar + -- ^ Constant ciphertext input buffer + -> CULLong + -- ^ Length of ciphertext input buffer + -> Ptr CChar + -- ^ Constant aad input buffer + -> CULLong + -- ^ Length of aad input buffer + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt + +-- | The aead C API uses C strings. Always returns 0. +foreign import ccall "crypto_aead_chacha20poly1305_encrypt_detached" + c_aead_detached + :: Ptr CChar + -- ^ Cipher output buffer + -> Ptr CChar + -- ^ Tag output buffer + -> Ptr CULLong + -- ^ Tag bytes used + -> Ptr CChar + -- ^ Constant message input buffer + -> CULLong + -- ^ Length of message input buffer + -> Ptr CChar + -- ^ Constant aad input buffer + -> CULLong + -- ^ Length of aad input buffer + -> Ptr CChar + -- ^ Unused 'nsec' value (must be NULL) + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt + +-- | The aead open C API uses C strings. Returns 0 if successful. +foreign import ccall "crypto_aead_chacha20poly1305_decrypt_detached" + c_aead_open_detached + :: Ptr CChar + -- ^ Message output buffer + -> Ptr CChar + -- ^ Unused 'nsec' value (must be NULL) + -> Ptr CChar + -- ^ Constant ciphertext input buffer + -> CULLong + -- ^ Length of ciphertext input buffer + -> Ptr CChar + -- ^ Constant tag input buffer + -> Ptr CChar + -- ^ Constant aad input buffer + -> CULLong + -- ^ Length of aad input buffer + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt diff --git a/src/Crypto/Saltine/Internal/AEAD/ChaCha20Poly1305IETF.hs b/src/Crypto/Saltine/Internal/AEAD/ChaCha20Poly1305IETF.hs new file mode 100644 index 00000000..4e7b91a2 --- /dev/null +++ b/src/Crypto/Saltine/Internal/AEAD/ChaCha20Poly1305IETF.hs @@ -0,0 +1,183 @@ +{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving, DeriveGeneric, ForeignFunctionInterface #-} +-- | +-- Module : Crypto.Saltine.Internal.AEAD.ChaCha20Poly1305IETF +-- Copyright : (c) Max Amanshauser 2021 +-- License : MIT +-- +-- Maintainer : max@lambdalifting.org +-- Stability : experimental +-- Portability : non-portable +-- +module Crypto.Saltine.Internal.AEAD.ChaCha20Poly1305IETF ( + aead_chacha20poly1305_ietf_keybytes + , aead_chacha20poly1305_ietf_npubbytes + , aead_chacha20poly1305_ietf_abytes + , c_aead + , c_aead_open + , c_aead_detached + , c_aead_open_detached + , Key(..) + , Nonce(..) +) where + +import Control.DeepSeq +import Crypto.Saltine.Class +import Crypto.Saltine.Core.Hash (shorthash) +import Crypto.Saltine.Internal.Hash (nullShKey) +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) +import Data.Data (Data, Typeable) +import Data.Hashable (Hashable) +import Data.Monoid +import Foreign.C +import Foreign.Ptr +import GHC.Generics (Generic) + +import qualified Data.ByteString as S + + +-- | An opaque 'ChaCha20Poly1305IETF' cryptographic key. +newtype Key = Key { unKey :: ByteString } deriving (Ord, Hashable, Data, Typeable, Generic, NFData) +instance Eq Key where + Key a == Key b = U.compare a b +instance Show Key where + show k = "AEAD.ChaCha20Poly1305IETF.Key {hashesTo = \"" <> (bin2hex . shorthash nullShKey $ encode k) <> "\"}" + +instance IsEncoding Key where + decode v = if S.length v == aead_chacha20poly1305_ietf_keybytes + then Just (Key v) + else Nothing + {-# INLINE decode #-} + encode (Key v) = v + {-# INLINE encode #-} + +-- | An opaque 'ChaCha20Poly1305IETF' nonce. +newtype Nonce = Nonce { unNonce :: ByteString } deriving (Eq, Ord, Hashable, Data, Typeable, Generic, NFData) +instance Show Nonce where + show k = "AEAD.ChaCha20Poly1305IETF.Nonce " <> bin2hex (encode k) + +instance IsEncoding Nonce where + decode v = if S.length v == aead_chacha20poly1305_ietf_npubbytes + then Just (Nonce v) + else Nothing + {-# INLINE decode #-} + encode (Nonce v) = v + {-# INLINE encode #-} + +instance IsNonce Nonce where + zero = Nonce (S.replicate aead_chacha20poly1305_ietf_npubbytes 0) + nudge (Nonce n) = Nonce (nudgeBS n) + +aead_chacha20poly1305_ietf_keybytes, aead_chacha20poly1305_ietf_abytes, aead_chacha20poly1305_ietf_npubbytes :: Int + +-- | Size of a ChaCha20-Poly1305-IETF key +aead_chacha20poly1305_ietf_keybytes = fromIntegral c_crypto_aead_chacha20poly1305_ietf_keybytes +-- | Size of a ChaCha20-Poly1305-IETF nonce +aead_chacha20poly1305_ietf_npubbytes = fromIntegral c_crypto_aead_chacha20poly1305_ietf_npubbytes +-- | Size of a ChaCha20-Poly1305-IETF authentication tag +aead_chacha20poly1305_ietf_abytes = fromIntegral c_crypto_aead_chacha20poly1305_ietf_abytes + + +-- src/libsodium/crypto_aead/chacha20poly1305/sodium/aead_chacha20poly1305.c +-- src/libsodium/include/sodium/crypto_aead_chacha20poly1305.h +foreign import ccall "crypto_aead_chacha20poly1305_ietf_keybytes" + c_crypto_aead_chacha20poly1305_ietf_keybytes :: CSize +foreign import ccall "crypto_aead_chacha20poly1305_ietf_npubbytes" + c_crypto_aead_chacha20poly1305_ietf_npubbytes:: CSize +foreign import ccall "crypto_aead_chacha20poly1305_ietf_abytes" + c_crypto_aead_chacha20poly1305_ietf_abytes :: CSize + + +-- | The aead C API uses C strings. Always returns 0. +foreign import ccall "crypto_aead_chacha20poly1305_ietf_encrypt" + c_aead + :: Ptr CChar + -- ^ Cipher output buffer + -> Ptr CULLong + -- ^ Cipher output bytes used + -> Ptr CChar + -- ^ Constant message input buffer + -> CULLong + -- ^ Length of message input buffer + -> Ptr CChar + -- ^ Constant aad input buffer + -> CULLong + -- ^ Length of aad input buffer + -> Ptr CChar + -- ^ Unused 'nsec' value (must be NULL) + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt + +-- | The aead open C API uses C strings. Returns 0 if successful. +foreign import ccall "crypto_aead_chacha20poly1305_ietf_decrypt" + c_aead_open + :: Ptr CChar + -- ^ Message output buffer + -> Ptr CULLong + -- ^ Message output bytes used + -> Ptr CChar + -- ^ Unused 'nsec' value (must be NULL) + -> Ptr CChar + -- ^ Constant ciphertext input buffer + -> CULLong + -- ^ Length of ciphertext input buffer + -> Ptr CChar + -- ^ Constant aad input buffer + -> CULLong + -- ^ Length of aad input buffer + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt + +-- | The aead C API uses C strings. Always returns 0. +foreign import ccall "crypto_aead_chacha20poly1305_ietf_encrypt_detached" + c_aead_detached + :: Ptr CChar + -- ^ Cipher output buffer + -> Ptr CChar + -- ^ Tag output buffer + -> Ptr CULLong + -- ^ Tag bytes used + -> Ptr CChar + -- ^ Constant message input buffer + -> CULLong + -- ^ Length of message input buffer + -> Ptr CChar + -- ^ Constant aad input buffer + -> CULLong + -- ^ Length of aad input buffer + -> Ptr CChar + -- ^ Unused 'nsec' value (must be NULL) + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt + +-- | The aead open C API uses C strings. Returns 0 if successful. +foreign import ccall "crypto_aead_chacha20poly1305_ietf_decrypt_detached" + c_aead_open_detached + :: Ptr CChar + -- ^ Message output buffer + -> Ptr CChar + -- ^ Unused 'nsec' value (must be NULL) + -> Ptr CChar + -- ^ Constant ciphertext input buffer + -> CULLong + -- ^ Length of ciphertext input buffer + -> Ptr CChar + -- ^ Constant tag input buffer + -> Ptr CChar + -- ^ Constant aad input buffer + -> CULLong + -- ^ Length of aad input buffer + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt diff --git a/src/Crypto/Saltine/Internal/AEAD/XChaCha20Poly1305.hs b/src/Crypto/Saltine/Internal/AEAD/XChaCha20Poly1305.hs new file mode 100644 index 00000000..bf3b58bf --- /dev/null +++ b/src/Crypto/Saltine/Internal/AEAD/XChaCha20Poly1305.hs @@ -0,0 +1,183 @@ +{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving, DeriveGeneric, ForeignFunctionInterface #-} +-- | +-- Module : Crypto.Saltine.Internal.AEAD.XChaCha20Poly1305 +-- Copyright : (c) Max Amanshauser 2021 +-- License : MIT +-- +-- Maintainer : max@lambdalifting.org +-- Stability : experimental +-- Portability : non-portable +-- +module Crypto.Saltine.Internal.AEAD.XChaCha20Poly1305 ( + aead_xchacha20poly1305_ietf_keybytes + , aead_xchacha20poly1305_ietf_npubbytes + , aead_xchacha20poly1305_ietf_abytes + , c_aead + , c_aead_open + , c_aead_detached + , c_aead_open_detached + , Key(..) + , Nonce(..) +) where + +import Control.DeepSeq +import Crypto.Saltine.Class +import Crypto.Saltine.Core.Hash (shorthash) +import Crypto.Saltine.Internal.Hash (nullShKey) +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) +import Data.Data (Data, Typeable) +import Data.Hashable (Hashable) +import Data.Monoid +import Foreign.C +import Foreign.Ptr +import GHC.Generics (Generic) + +import qualified Data.ByteString as S + +-- | An opaque 'XChaCha20Poly1305' cryptographic key. +newtype Key = Key { unKey :: ByteString } deriving (Ord, Hashable, Data, Typeable, Generic, NFData) +instance Eq Key where + Key a == Key b = U.compare a b +instance Show Key where + show k = "AEAD.XChaCha20Poly1305.Key {hashesTo = \"" <> (bin2hex . shorthash nullShKey $ encode k) <> "\"}" + +instance IsEncoding Key where + decode v = if S.length v == aead_xchacha20poly1305_ietf_keybytes + then Just (Key v) + else Nothing + {-# INLINE decode #-} + encode (Key v) = v + {-# INLINE encode #-} + +-- | An opaque 'XChaCha20Poly1305' nonce. +newtype Nonce = Nonce { unNonce :: ByteString } deriving (Eq, Ord, Hashable, Data, Typeable, Generic, NFData) +instance Show Nonce where + show k = "AEAD.XChaCha20Poly1305.Nonce " <> bin2hex (encode k) + +instance IsEncoding Nonce where + decode v = if S.length v == aead_xchacha20poly1305_ietf_npubbytes + then Just (Nonce v) + else Nothing + {-# INLINE decode #-} + encode (Nonce v) = v + {-# INLINE encode #-} + +instance IsNonce Nonce where + zero = Nonce (S.replicate aead_xchacha20poly1305_ietf_npubbytes 0) + nudge (Nonce n) = Nonce (nudgeBS n) + + +aead_xchacha20poly1305_ietf_keybytes, aead_xchacha20poly1305_ietf_abytes, aead_xchacha20poly1305_ietf_npubbytes :: Int + +-- | Size of a XChaCha20-Poly1305 key +aead_xchacha20poly1305_ietf_keybytes = fromIntegral c_crypto_aead_xchacha20poly1305_ietf_keybytes +-- | Size of a XChaCha20-Poly1305 nonce +aead_xchacha20poly1305_ietf_npubbytes = fromIntegral c_crypto_aead_xchacha20poly1305_ietf_npubbytes +-- | Size of a XChaCha20-Poly1305 authentication tag +aead_xchacha20poly1305_ietf_abytes = fromIntegral c_crypto_aead_xchacha20poly1305_ietf_abytes + + +-- src/libsodium/crypto_aead/xchacha20poly1305/sodium/aead_xchacha20poly1305.c +-- src/libsodium/include/sodium/crypto_aead_xchacha20poly1305.h +foreign import ccall "crypto_aead_xchacha20poly1305_ietf_keybytes" + c_crypto_aead_xchacha20poly1305_ietf_keybytes :: CSize +foreign import ccall "crypto_aead_xchacha20poly1305_ietf_npubbytes" + c_crypto_aead_xchacha20poly1305_ietf_npubbytes:: CSize +foreign import ccall "crypto_aead_xchacha20poly1305_ietf_abytes" + c_crypto_aead_xchacha20poly1305_ietf_abytes :: CSize + + +-- | The aead C API uses C strings. Always returns 0. +foreign import ccall "crypto_aead_xchacha20poly1305_ietf_encrypt" + c_aead + :: Ptr CChar + -- ^ Cipher output buffer + -> Ptr CULLong + -- ^ Cipher output bytes used + -> Ptr CChar + -- ^ Constant message input buffer + -> CULLong + -- ^ Length of message input buffer + -> Ptr CChar + -- ^ Constant aad input buffer + -> CULLong + -- ^ Length of aad input buffer + -> Ptr CChar + -- ^ Unused 'nsec' value (must be NULL) + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt + +-- | The aead open C API uses C strings. Returns 0 if successful. +foreign import ccall "crypto_aead_xchacha20poly1305_ietf_decrypt" + c_aead_open + :: Ptr CChar + -- ^ Message output buffer + -> Ptr CULLong + -- ^ Message output bytes used + -> Ptr CChar + -- ^ Unused 'nsec' value (must be NULL) + -> Ptr CChar + -- ^ Constant ciphertext input buffer + -> CULLong + -- ^ Length of ciphertext input buffer + -> Ptr CChar + -- ^ Constant aad input buffer + -> CULLong + -- ^ Length of aad input buffer + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt + +-- | The aead C API uses C strings. Always returns 0. +foreign import ccall "crypto_aead_xchacha20poly1305_ietf_encrypt_detached" + c_aead_detached + :: Ptr CChar + -- ^ Cipher output buffer + -> Ptr CChar + -- ^ Tag output buffer + -> Ptr CULLong + -- ^ Tag bytes used + -> Ptr CChar + -- ^ Constant message input buffer + -> CULLong + -- ^ Length of message input buffer + -> Ptr CChar + -- ^ Constant aad input buffer + -> CULLong + -- ^ Length of aad input buffer + -> Ptr CChar + -- ^ Unused 'nsec' value (must be NULL) + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt + +-- | The aead open C API uses C strings. Returns 0 if successful. +foreign import ccall "crypto_aead_xchacha20poly1305_ietf_decrypt_detached" + c_aead_open_detached + :: Ptr CChar + -- ^ Message output buffer + -> Ptr CChar + -- ^ Unused 'nsec' value (must be NULL) + -> Ptr CChar + -- ^ Constant ciphertext input buffer + -> CULLong + -- ^ Length of ciphertext input buffer + -> Ptr CChar + -- ^ Constant tag input buffer + -> Ptr CChar + -- ^ Constant aad input buffer + -> CULLong + -- ^ Length of aad input buffer + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt diff --git a/src/Crypto/Saltine/Internal/Auth.hs b/src/Crypto/Saltine/Internal/Auth.hs new file mode 100644 index 00000000..06c94ac3 --- /dev/null +++ b/src/Crypto/Saltine/Internal/Auth.hs @@ -0,0 +1,103 @@ +{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving, DeriveGeneric, ForeignFunctionInterface #-} +-- | +-- Module : Crypto.Saltine.Internal.Auth +-- Copyright : (c) Max Amanshauser 2021 +-- License : MIT +-- +-- Maintainer : max@lambdalifting.org +-- Stability : experimental +-- Portability : non-portable +-- +module Crypto.Saltine.Internal.Auth ( + auth_bytes + , auth_keybytes + , c_auth + , c_auth_verify + , Key(..) + , Authenticator(..) +) where + + +import Control.DeepSeq +import Crypto.Saltine.Class +import Crypto.Saltine.Core.Hash (shorthash) +import Crypto.Saltine.Internal.Hash (nullShKey) +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) +import Data.Data (Data, Typeable) +import Data.Hashable (Hashable) +import Data.Monoid +import GHC.Generics (Generic) +import Foreign.C +import Foreign.Ptr + +import qualified Data.ByteString as S + +-- | An opaque 'auth' cryptographic key. +newtype Key = Key { unKey :: ByteString } deriving (Ord, Hashable, Data, Typeable, Generic, NFData) +instance Eq Key where + Key a == Key b = U.compare a b +instance Show Key where + show k = "Auth.Key {hashesTo = \"" <> (bin2hex . shorthash nullShKey $ encode k) <> "\"}" + +instance IsEncoding Key where + decode v = if S.length v == auth_keybytes + then Just (Key v) + else Nothing + {-# INLINE decode #-} + encode (Key v) = v + {-# INLINE encode #-} + +-- | An opaque 'auth' authenticator. +newtype Authenticator = Au { unAu :: ByteString } deriving (Eq, Ord, Hashable, Data, Typeable, Generic, NFData) +instance Show Authenticator where + show k = "Auth.Authenticator " <> bin2hex (encode k) + +instance IsEncoding Authenticator where + decode v = if S.length v == auth_bytes + then Just (Au v) + else Nothing + {-# INLINE decode #-} + encode (Au v) = v + {-# INLINE encode #-} + + +auth_bytes, auth_keybytes :: Int + +-- Authentication +-- | Size of a @crypto_auth@ authenticator. +auth_bytes = fromIntegral c_crypto_auth_bytes +-- | Size of a @crypto_auth@ authenticator key. +auth_keybytes = fromIntegral c_crypto_auth_keybytes + +-- src/libsodium/crypto_auth/crypto_auth.c +foreign import ccall "crypto_auth_bytes" + c_crypto_auth_bytes :: CSize +foreign import ccall "crypto_auth_keybytes" + c_crypto_auth_keybytes :: CSize + +foreign import ccall "crypto_auth" + c_auth :: Ptr CChar + -- ^ Authenticator output buffer + -> Ptr CChar + -- ^ Constant message buffer + -> CULLong + -- ^ Length of message buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt + -- ^ Always 0 + +-- | We don't even include this in the IO monad since all of the +-- buffers are constant. +foreign import ccall "crypto_auth_verify" + c_auth_verify :: Ptr CChar + -- ^ Constant authenticator buffer + -> Ptr CChar + -- ^ Constant message buffer + -> CULLong + -- ^ Length of message buffer + -> Ptr CChar + -- ^ Constant key buffer + -> CInt + -- ^ Success if 0, failure if -1 diff --git a/src/Crypto/Saltine/Internal/Box.hs b/src/Crypto/Saltine/Internal/Box.hs new file mode 100644 index 00000000..8eb5c1df --- /dev/null +++ b/src/Crypto/Saltine/Internal/Box.hs @@ -0,0 +1,285 @@ +{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving, DeriveGeneric, ForeignFunctionInterface #-} +-- | +-- Module : Crypto.Saltine.Internal.Box +-- Copyright : (c) Max Amanshauser 2021 +-- License : MIT +-- +-- Maintainer : max@lambdalifting.org +-- Stability : experimental +-- Portability : non-portable +-- +module Crypto.Saltine.Internal.Box ( + box_publickeybytes + , box_secretkeybytes + , box_noncebytes + , box_zerobytes + , box_boxzerobytes + , box_macbytes + , box_beforenmbytes + , box_sealbytes + , c_box_keypair + , c_box_easy + , c_box_open_easy + , c_box_beforenm + , c_box_easy_afternm + , c_box_open_easy_afternm + , c_box_seal + , c_box_seal_open + , SecretKey(..) + , PublicKey(..) + , Keypair(..) + , CombinedKey(..) + , Nonce(..) +) where + +import Control.DeepSeq (NFData) +import Crypto.Saltine.Class +import Crypto.Saltine.Core.Hash (shorthash) +import Crypto.Saltine.Internal.Hash (nullShKey) +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) +import Data.Data (Data, Typeable) +import Data.Hashable (Hashable) +import Data.Monoid +import Foreign.C +import Foreign.Ptr +import GHC.Generics (Generic) + +import qualified Data.ByteString as S + +-- | An opaque 'box' cryptographic secret key. +newtype SecretKey = SK { unSK :: ByteString } deriving (Ord, Hashable, Data, Typeable, Generic, NFData) +instance Eq SecretKey where + SK a == SK b = U.compare a b +instance Show SecretKey where + show k = "Box.SecretKey {hashesTo = \"" <> (bin2hex . shorthash nullShKey $ encode k) <> "\"}" + +instance IsEncoding SecretKey where + decode v = if S.length v == box_secretkeybytes + then Just (SK v) + else Nothing + {-# INLINE decode #-} + encode (SK v) = v + {-# INLINE encode #-} + +-- | An opaque 'box' cryptographic public key. +newtype PublicKey = PK { unPK :: ByteString } deriving (Ord, Hashable, Data, Typeable, Generic, NFData) +instance Eq PublicKey where + PK a == PK b = U.compare a b +instance Show PublicKey where + show k = "Box.PublicKey {hashesTo = \"" <> (bin2hex . shorthash nullShKey $ encode k) <> "\"}" + +instance IsEncoding PublicKey where + decode v = if S.length v == box_publickeybytes + then Just (PK v) + else Nothing + {-# INLINE decode #-} + encode (PK v) = v + {-# INLINE encode #-} + +-- | A convenience type for keypairs +data Keypair = Keypair { + secretKey :: SecretKey + , publicKey :: PublicKey +} deriving (Ord, Data, Typeable, Generic) + +instance Eq Keypair where + kp1 == kp2 = U.compare (encode $ secretKey kp1) (encode $ secretKey kp2) + !&&! U.compare (encode $ publicKey kp1) (encode $ publicKey kp2) + +instance Show Keypair where + show k = "Box.Keypair {secretKey = " <> show (secretKey k) <> ", publicKey = " <> show (publicKey k) <> "}" + +instance Hashable Keypair +instance NFData Keypair + +-- | An opaque 'boxAfterNM' cryptographic combined key. +newtype CombinedKey = CK { unCK :: ByteString } deriving (Ord, Hashable, Data, Typeable, Generic, NFData) +instance Eq CombinedKey where + CK a == CK b = U.compare a b +instance Show CombinedKey where + show k = "Box.CombinedKey {hashesTo = \"" <> (bin2hex . shorthash nullShKey $ encode k) <> "\"}" + +instance IsEncoding CombinedKey where + decode v = if S.length v == box_beforenmbytes + then Just (CK v) + else Nothing + {-# INLINE decode #-} + encode (CK v) = v + {-# INLINE encode #-} + +-- | An opaque 'box' nonce. +newtype Nonce = Nonce { unNonce :: ByteString } deriving (Eq, Ord, Hashable, Data, Typeable, Generic, NFData) +instance Show Nonce where + show k = "Box.Nonce " <> bin2hex (encode k) + +instance IsEncoding Nonce where + decode v = if S.length v == box_noncebytes + then Just (Nonce v) + else Nothing + {-# INLINE decode #-} + encode (Nonce v) = v + {-# INLINE encode #-} + +instance IsNonce Nonce where + zero = Nonce (S.replicate box_noncebytes 0) + nudge (Nonce n) = Nonce (nudgeBS n) + + +box_publickeybytes, box_secretkeybytes, box_noncebytes, box_zerobytes, box_boxzerobytes :: Int +box_macbytes, box_beforenmbytes, box_sealbytes :: Int + +-- Box +-- | Size of a @crypto_box@ public key +box_publickeybytes = fromIntegral c_crypto_box_publickeybytes +-- | Size of a @crypto_box@ secret key +box_secretkeybytes = fromIntegral c_crypto_box_secretkeybytes +-- | Size of a @crypto_box@ nonce +box_noncebytes = fromIntegral c_crypto_box_noncebytes +-- | Size of 0-padding prepended to messages before using @crypto_box@ +-- or after using @crypto_box_open@ +box_zerobytes = fromIntegral c_crypto_box_zerobytes +-- | Size of 0-padding prepended to ciphertext before using +-- @crypto_box_open@ or after using @crypto_box@. +box_boxzerobytes = fromIntegral c_crypto_box_boxzerobytes +box_macbytes = fromIntegral c_crypto_box_macbytes +-- | Size of a @crypto_box_beforenm@-generated combined key +box_beforenmbytes = fromIntegral c_crypto_box_beforenmbytes + +-- SealedBox +-- | Amount by which ciphertext is longer than plaintext +-- in sealed boxes +box_sealbytes = fromIntegral c_crypto_box_sealbytes + +-- src/libsodium/crypto_box/crypto_box.c +foreign import ccall "crypto_box_publickeybytes" + c_crypto_box_publickeybytes :: CSize +foreign import ccall "crypto_box_secretkeybytes" + c_crypto_box_secretkeybytes :: CSize +foreign import ccall "crypto_box_beforenmbytes" + c_crypto_box_beforenmbytes :: CSize +foreign import ccall "crypto_box_noncebytes" + c_crypto_box_noncebytes :: CSize +foreign import ccall "crypto_box_zerobytes" + c_crypto_box_zerobytes :: CSize +foreign import ccall "crypto_box_boxzerobytes" + c_crypto_box_boxzerobytes :: CSize +foreign import ccall "crypto_box_macbytes" + c_crypto_box_macbytes :: CSize + +-- src/libsodium/crypto_box_seal.c +foreign import ccall "crypto_box_sealbytes" + c_crypto_box_sealbytes :: CSize + +-- | Should always return a 0. +foreign import ccall "crypto_box_keypair" + c_box_keypair :: Ptr CChar + -- ^ Public key + -> Ptr CChar + -- ^ Secret key + -> IO CInt + -- ^ Always 0 + +-- | The secretbox C API uses C strings. +foreign import ccall "crypto_box_easy" + c_box_easy :: Ptr CChar + -- ^ Cipher output buffer + -> Ptr CChar + -- ^ Constant message input buffer + -> CULLong + -- ^ Length of message input buffer + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant public key buffer + -> Ptr CChar + -- ^ Constant secret key buffer + -> IO CInt + -- ^ Always 0 + +-- | The secretbox C API uses C strings. +foreign import ccall "crypto_box_open_easy" + c_box_open_easy :: Ptr CChar + -- ^ Message output buffer + -> Ptr CChar + -- ^ Constant ciphertext input buffer + -> CULLong + -- ^ Length of message input buffer + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant public key buffer + -> Ptr CChar + -- ^ Constant secret key buffer + -> IO CInt + -- ^ 0 for success, -1 for failure to verify + +-- | Single target key precompilation. +foreign import ccall "crypto_box_beforenm" + c_box_beforenm :: Ptr CChar + -- ^ Combined key output buffer + -> Ptr CChar + -- ^ Constant public key buffer + -> Ptr CChar + -- ^ Constant secret key buffer + -> IO CInt + -- ^ Always 0 + +-- | Precompiled key crypto box. Uses C strings. +foreign import ccall "crypto_box_easy_afternm" + c_box_easy_afternm :: Ptr CChar + -- ^ Cipher output buffer + -> Ptr CChar + -- ^ Constant message input buffer + -> CULLong + -- ^ Length of message input buffer (incl. 0s) + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant combined key buffer + -> IO CInt + -- ^ Always 0 + +-- | The secretbox C API uses C strings. +foreign import ccall "crypto_box_open_easy_afternm" + c_box_open_easy_afternm :: Ptr CChar + -- ^ Message output buffer + -> Ptr CChar + -- ^ Constant ciphertext input buffer + -> CULLong + -- ^ Length of message input buffer (incl. 0s) + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant combined key buffer + -> IO CInt + -- ^ 0 for success, -1 for failure to verify + + +-- | The sealedbox C API uses C strings. +foreign import ccall "crypto_box_seal" + c_box_seal :: Ptr CChar + -- ^ Cipher output buffer + -> Ptr CChar + -- ^ Constant message input buffer + -> CULLong + -- ^ Length of message input buffer + -> Ptr CChar + -- ^ Constant public key buffer + -> IO CInt + -- ^ Always 0 + +-- | The sealedbox C API uses C strings. +foreign import ccall "crypto_box_seal_open" + c_box_seal_open :: Ptr CChar + -- ^ Message output buffer + -> Ptr CChar + -- ^ Constant ciphertext input buffer + -> CULLong + -- ^ Length of message input buffer + -> Ptr CChar + -- ^ Constant public key buffer + -> Ptr CChar + -- ^ Constant secret key buffer + -> IO CInt + -- ^ 0 for success, -1 for failure to decrypt diff --git a/src/Crypto/Saltine/Internal/ByteSizes.hs b/src/Crypto/Saltine/Internal/ByteSizes.hs new file mode 100644 index 00000000..62186f96 --- /dev/null +++ b/src/Crypto/Saltine/Internal/ByteSizes.hs @@ -0,0 +1,193 @@ +-- | +-- Module : Crypto.Saltine.Internal.ByteSizes +-- Copyright : (c) Joseph Abrahamson 2013 +-- (c) Max Amanshauser 2021 +-- License : MIT +-- +-- Maintainer : max@lambdalifting.org +-- Stability : experimental +-- Portability : non-portable +-- +module Crypto.Saltine.Internal.ByteSizes ( + ) where + + +-- Others +-- ------ + +-- src/libsodium/crypto_auth/hmacsha256/auth_hmacsha256_api.c +-- foreign import ccall "crypto_auth_hmacsha256_bytes" +-- c_crypto_auth_hmacsha256_bytes :: CSize +-- foreign import ccall "crypto_auth_hmacsha256_keybytes" +-- c_crypto_auth_hmacsha256_keybytes :: CSize + +-- src/libsodium/crypto_auth/hmacsha512256/auth_hmacsha512256_api.c +-- foreign import ccall "crypto_auth_hmacsha512256_bytes" +-- c_crypto_auth_hmacsha512256_bytes :: CSize +-- foreign import ccall "crypto_auth_hmacsha512256_keybytes" +-- c_crypto_auth_hmacsha512256_keybytes :: CSize + +-- src/libsodium/crypto_box/curve25519xsalsa20poly1305/box_curve25519xsalsa20poly1305_api.c +-- foreign import ccall "crypto_box_curve25519xsalsa20poly1305_publickeybytes" +-- c_crypto_box_curve25519xsalsa20poly1305_publickeybytes :: CSize +-- foreign import ccall "crypto_box_curve25519xsalsa20poly1305_secretkeybytes" +-- c_crypto_box_curve25519xsalsa20poly1305_secretkeybytes :: CSize +-- foreign import ccall "crypto_box_curve25519xsalsa20poly1305_beforenmbytes" +-- c_crypto_box_curve25519xsalsa20poly1305_beforenmbytes :: CSize +-- foreign import ccall "crypto_box_curve25519xsalsa20poly1305_noncebytes" +-- c_crypto_box_curve25519xsalsa20poly1305_noncebytes :: CSize +-- foreign import ccall "crypto_box_curve25519xsalsa20poly1305_zerobytes" +-- c_crypto_box_curve25519xsalsa20poly1305_zerobytes :: CSize +-- foreign import ccall "crypto_box_curve25519xsalsa20poly1305_boxzerobytes" +-- c_crypto_box_curve25519xsalsa20poly1305_boxzerobytes :: CSize +-- foreign import ccall "crypto_box_curve25519xsalsa20poly1305_macbytes" +-- c_crypto_box_curve25519xsalsa20poly1305_macbytes :: CSize + +-- src/libsodium/crypto_core/hsalsa20/core_hsalsa20_api.c +-- foreign import ccall "crypto_core_hsalsa20_outputbytes" +-- c_crypto_core_hsalsa20_outputbytes :: CSize +-- foreign import ccall "crypto_core_hsalsa20_inputbytes" +-- c_crypto_core_hsalsa20_inputbytes :: CSize +-- foreign import ccall "crypto_core_hsalsa20_keybytes" +-- c_crypto_core_hsalsa20_keybytes :: CSize +-- foreign import ccall "crypto_core_hsalsa20_constbytes" +-- c_crypto_core_hsalsa20_constbytes :: CSize + +-- src/libsodium/crypto_core/salsa20/core_salsa20_api.c +-- foreign import ccall "crypto_core_salsa20_outputbytes" +-- c_crypto_core_salsa20_outputbytes :: CSize +-- foreign import ccall "crypto_core_salsa20_inputbytes" +-- c_crypto_core_salsa20_inputbytes :: CSize +-- foreign import ccall "crypto_core_salsa20_keybytes" +-- c_crypto_core_salsa20_keybytes :: CSize +-- foreign import ccall "crypto_core_salsa20_constbytes" +-- c_crypto_core_salsa20_constbytes :: CSize + +-- src/libsodium/crypto_core/salsa2012/core_salsa2012_api.c +-- foreign import ccall "crypto_core_salsa2012_outputbytes" +-- c_crypto_core_salsa2012_outputbytes :: CSize +-- foreign import ccall "crypto_core_salsa2012_inputbytes" +-- c_crypto_core_salsa2012_inputbytes :: CSize +-- foreign import ccall "crypto_core_salsa2012_keybytes" +-- c_crypto_core_salsa2012_keybytes :: CSize +-- foreign import ccall "crypto_core_salsa2012_constbytes" +-- c_crypto_core_salsa2012_constbytes :: CSize + +-- src/libsodium/crypto_core/salsa208/core_salsa208_api.c +-- foreign import ccall "crypto_core_salsa208_outputbytes" +-- c_crypto_core_salsa208_outputbytes :: CSize +-- foreign import ccall "crypto_core_salsa208_inputbytes" +-- c_crypto_core_salsa208_inputbytes :: CSize +-- foreign import ccall "crypto_core_salsa208_keybytes" +-- c_crypto_core_salsa208_keybytes :: CSize +-- foreign import ccall "crypto_core_salsa208_constbytes" +-- c_crypto_core_salsa208_constbytes :: CSize + +-- src/libsodium/crypto_generichash/blake2/generichash_blake2_api.c +-- foreign import ccall "crypto_generichash_blake2b_blockbytes" +-- c_crypto_generichash_blake2b_blockbytes :: CSize + +-- src/libsodium/crypto_generichash/crypto_generichash.c +-- foreign import ccall "crypto_generichash_bytes" +-- c_crypto_generichash_bytes :: CSize +-- foreign import ccall "crypto_generichash_keybytes" +-- c_crypto_generichash_keybytes :: CSize +-- foreign import ccall "crypto_generichash_blockbytes" +-- c_crypto_generichash_blockbytes :: CSize + +-- src/libsodium/crypto_hash/sha256/hash_sha256_api.c +-- foreign import ccall "crypto_hash_sha256_bytes" +-- c_crypto_hash_sha256_bytes :: CSize + +-- src/libsodium/crypto_hash/sha512/hash_sha512_api.c +-- foreign import ccall "crypto_hash_sha512_bytes" +-- c_crypto_hash_sha512_bytes :: CSize + +-- src/libsodium/crypto_hashblocks/sha256/hashblocks_sha256_api.c +-- foreign import ccall "crypto_hashblocks_sha256_statebytes" +-- c_crypto_hashblocks_sha256_statebytes :: CSize +-- foreign import ccall "crypto_hashblocks_sha256_blockbytes" +-- c_crypto_hashblocks_sha256_blockbytes :: CSize + +-- src/libsodium/crypto_hashblocks/sha512/hashblocks_sha512_api.c +-- foreign import ccall "crypto_hashblocks_sha512_statebytes" +-- c_crypto_hashblocks_sha512_statebytes :: CSize +-- foreign import ccall "crypto_hashblocks_sha512_blockbytes" +-- c_crypto_hashblocks_sha512_blockbytes :: CSize + +-- src/libsodium/crypto_onetimeauth/poly1305/onetimeauth_poly1305_api.c +-- foreign import ccall "crypto_onetimeauth_poly1305_bytes" +-- c_crypto_onetimeauth_poly1305_bytes :: CSize +-- foreign import ccall "crypto_onetimeauth_poly1305_keybytes" +-- c_crypto_onetimeauth_poly1305_keybytes :: CSize + +-- src/libsodium/crypto_secretbox/xsalsa20poly1305/secretbox_xsalsa20poly1305_api.c +-- foreign import ccall "crypto_secretbox_xsalsa20poly1305_keybytes" +-- c_crypto_secretbox_xsalsa20poly1305_keybytes :: CSize +-- foreign import ccall "crypto_secretbox_xsalsa20poly1305_noncebytes" +-- c_crypto_secretbox_xsalsa20poly1305_noncebytes :: CSize +-- foreign import ccall "crypto_secretbox_xsalsa20poly1305_zerobytes" +-- c_crypto_secretbox_xsalsa20poly1305_zerobytes :: CSize +-- foreign import ccall "crypto_secretbox_xsalsa20poly1305_boxzerobytes" +-- c_crypto_secretbox_xsalsa20poly1305_boxzerobytes :: CSize + +-- foreign import ccall "crypto_shorthash_siphash24_bytes" +-- c_crypto_shorthash_siphash24_bytes :: CSize + +-- src/libsodium/crypto_sign/ed25519/sign_ed25519_api.c +-- foreign import ccall "crypto_sign_ed25519_bytes" +-- c_crypto_sign_ed25519_bytes :: CSize +-- foreign import ccall "crypto_sign_ed25519_publickeybytes" +-- c_crypto_sign_ed25519_publickeybytes :: CSize +-- foreign import ccall "crypto_sign_ed25519_secretkeybytes" +-- c_crypto_sign_ed25519_secretkeybytes :: CSize + +-- src/libsodium/crypto_sign/edwards25519sha512batch/sign_edwards25519sha512batch_api.c +-- foreign import ccall "crypto_sign_edwards25519sha512batch_bytes" +-- c_crypto_sign_edwards25519sha512batch_bytes :: CSize +-- foreign import ccall "crypto_sign_edwards25519sha512batch_publickeybytes" +-- c_crypto_sign_edwards25519sha512batch_publickeybytes :: CSize +-- foreign import ccall "crypto_sign_edwards25519sha512batch_secretkeybytes" +-- c_crypto_sign_edwards25519sha512batch_secretkeybytes :: CSize + +-- src/libsodium/crypto_stream/aes128ctr/stream_aes128ctr_api.c +-- foreign import ccall "crypto_stream_aes128ctr_keybytes" +-- c_crypto_stream_aes128ctr_keybytes :: CSize +-- foreign import ccall "crypto_stream_aes128ctr_noncebytes" +-- c_crypto_stream_aes128ctr_noncebytes :: CSize +-- foreign import ccall "crypto_stream_aes128ctr_beforenmbytes" +-- c_crypto_stream_aes128ctr_beforenmbytes :: CSize + +-- src/libsodium/crypto_stream/aes256estream/stream_aes256estream_api.c +-- foreign import ccall "crypto_stream_aes256estream_keybytes" +-- c_crypto_stream_aes256estream_keybytes :: CSize +-- foreign import ccall "crypto_stream_aes256estream_noncebytes" +-- c_crypto_stream_aes256estream_noncebytes :: CSize +-- foreign import ccall "crypto_stream_aes256estream_beforenmbytes" +-- c_crypto_stream_aes256estream_beforenmbytes :: CSize + +-- src/libsodium/crypto_stream/salsa2012/stream_salsa2012_api.c +-- foreign import ccall "crypto_stream_salsa2012_keybytes" +-- c_crypto_stream_salsa2012_keybytes :: CSize +-- foreign import ccall "crypto_stream_salsa2012_noncebytes" +-- c_crypto_stream_salsa2012_noncebytes :: CSize + +-- src/libsodium/crypto_stream/salsa208/stream_salsa208_api.c +-- foreign import ccall "crypto_stream_salsa208_keybytes" +-- c_crypto_stream_salsa208_keybytes :: CSize +-- foreign import ccall "crypto_stream_salsa208_noncebytes" +-- c_crypto_stream_salsa208_noncebytes :: CSize + +-- src/libsodium/crypto_stream/xsalsa20/stream_xsalsa20_api.c +-- foreign import ccall "crypto_stream_xsalsa20_keybytes" +-- c_crypto_stream_xsalsa20_keybytes :: CSize +-- foreign import ccall "crypto_stream_xsalsa20_noncebytes" +-- c_crypto_stream_xsalsa20_noncebytes :: CSize + +-- src/libsodium/crypto_verify/16/verify_16_api.c +-- foreign import ccall "crypto_verify_16_bytes" +-- c_crypto_verify_16_bytes :: CSize + +-- src/libsodium/crypto_verify/32/verify_32_api.c +-- foreign import ccall "crypto_verify_32_bytes" +-- c_crypto_verify_32_bytes :: CSize diff --git a/src/Crypto/Saltine/Internal/Hash.hs b/src/Crypto/Saltine/Internal/Hash.hs new file mode 100644 index 00000000..dd116a60 --- /dev/null +++ b/src/Crypto/Saltine/Internal/Hash.hs @@ -0,0 +1,162 @@ +{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving, DeriveGeneric, ForeignFunctionInterface #-} +-- | +-- Module : Crypto.Saltine.Internal.Hash +-- Copyright : (c) Max Amanshauser 2021 +-- License : MIT +-- +-- Maintainer : max@lambdalifting.org +-- Stability : experimental +-- Portability : non-portable +-- +module Crypto.Saltine.Internal.Hash ( + hash_bytes + , shorthash_bytes + , shorthash_keybytes + , generichash_bytes_max + , generichash_keybytes_max + , c_hash + , c_shorthash + , c_generichash + , nullShKey + , shorthash + , ShorthashKey(..) + , GenerichashKey(..) + , GenerichashOutLen(..) +) where + +import Control.DeepSeq +import Crypto.Saltine.Class +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) +import Data.Data (Data, Typeable) +import Data.Hashable (Hashable) +import Data.Monoid +import Foreign.C +import Foreign.Ptr +import GHC.Generics (Generic) + +import qualified Data.ByteString as S +import qualified Data.ByteString.Char8 as S8 + +-- | An opaque 'shorthash' cryptographic secret key. +newtype ShorthashKey = ShK { unShK :: ByteString } deriving (Ord, Hashable, Data, Typeable, Generic, NFData) +instance Eq ShorthashKey where + ShK a == ShK b = U.compare a b +instance Show ShorthashKey where + show k = "Hash.ShorthashKey {hashesTo = \"" <> (bin2hex . shorthash nullShKey $ encode k) <> "\"}" + +-- | Used for our `Show` instances +nullShKey :: ShorthashKey +nullShKey = ShK (S8.replicate shorthash_keybytes '\NUL') + +-- | Computes a very short, fast keyed hash. +-- This function is defined here to break circulat module imports +shorthash :: ShorthashKey + -> ByteString + -- ^ Message + -> ByteString + -- ^ Hash +shorthash (ShK k) m = snd . buildUnsafeByteString shorthash_bytes $ \ph -> + constByteStrings [k, m] $ \[(pk, _), (pm, _)] -> + c_shorthash ph pm (fromIntegral $ S.length m) pk + +instance IsEncoding ShorthashKey where + decode v = if S.length v == shorthash_keybytes + then Just (ShK v) + else Nothing + {-# INLINE decode #-} + encode (ShK v) = v + {-# INLINE encode #-} + +-- | An opaque 'generichash' cryptographic secret key. +newtype GenerichashKey = GhK { unGhK :: ByteString } deriving (Ord, Hashable, Data, Typeable, Generic, NFData) +instance Eq GenerichashKey where + GhK a == GhK b = U.compare a b +instance Show GenerichashKey where + show k = "Hash.GenerichashKey {hashesTo = \"" <> (bin2hex . shorthash nullShKey $ encode k) <> "\"}" + +instance IsEncoding GenerichashKey where + decode v = if S.length v <= generichash_keybytes_max + then Just (GhK v) + else Nothing + {-# INLINE decode #-} + encode (GhK v) = v + {-# INLINE encode #-} + +newtype GenerichashOutLen = GhOL { unGhOL :: Int } deriving (Eq, Ord, Hashable, Data, Typeable, Generic, NFData) + +hash_bytes, shorthash_bytes, shorthash_keybytes, generichash_bytes_max, generichash_keybytes_max :: Int + +-- Hashes +-- | The size of a hash resulting from +-- 'Crypto.Saltine.Internal.Hash.hash'. +hash_bytes = fromIntegral c_crypto_hash_bytes +-- | The size of a keyed hash resulting from +-- 'Crypto.Saltine.Internal.Hash.shorthash'. +shorthash_bytes = fromIntegral c_crypto_shorthash_bytes +-- | The size of a hashing key for the keyed hash function +-- 'Crypto.Saltine.Internal.Hash.shorthash'. +shorthash_keybytes = fromIntegral c_crypto_shorthash_keybytes +-- | The maximum output size of the generic hash function +-- 'Crypto.Saltine.Core.Hash.generichash' +generichash_bytes_max = fromIntegral c_crypto_generichash_bytes_max +-- | The maximum key size of the generic hash function +-- 'Crypto.Saltine.Core.Hash.generichash' +generichash_keybytes_max = fromIntegral c_crypto_generichash_keybytes_max + +-- src/libsodium/crypto_generichash/crypto_generichash.c +foreign import ccall "crypto_generichash_bytes_max" + c_crypto_generichash_bytes_max :: CSize +foreign import ccall "crypto_generichash_keybytes_max" + c_crypto_generichash_keybytes_max :: CSize + +-- src/libsodium/crypto_hash/crypto_hash.c +-- src/libsodium/include/sodium/crypto_hash.h +foreign import ccall "crypto_hash_bytes" + c_crypto_hash_bytes :: CSize + +-- src/libsodium/crypto_shorthash/crypto_shorthash.c +-- src/libsodium/include/sodium/crypto_shorthash.h +foreign import ccall "crypto_shorthash_bytes" + c_crypto_shorthash_bytes :: CSize +foreign import ccall "crypto_shorthash_keybytes" + c_crypto_shorthash_keybytes :: CSize + + +foreign import ccall "crypto_hash" + c_hash :: Ptr CChar + -- ^ Output hash buffer + -> Ptr CChar + -- ^ Constant message buffer + -> CULLong + -- ^ Constant message buffer length + -> IO CInt + -- ^ Always 0 + +foreign import ccall "crypto_shorthash" + c_shorthash :: Ptr CChar + -- ^ Output hash buffer + -> Ptr CChar + -- ^ Constant message buffer + -> CULLong + -- ^ Message buffer length + -> Ptr CChar + -- ^ Constant Key buffer + -> IO CInt + -- ^ Always 0 + +foreign import ccall "crypto_generichash" + c_generichash :: Ptr CChar + -- ^ Output hash buffer + -> CULLong + -- ^ Output hash length + -> Ptr CChar + -- ^ Constant message buffer + -> CULLong + -- ^ Message buffer length + -> Ptr CChar + -- ^ Constant Key buffer + -> CULLong + -- ^ Key buffer length + -> IO CInt + -- ^ Always 0 diff --git a/src/Crypto/Saltine/Internal/OneTimeAuth.hs b/src/Crypto/Saltine/Internal/OneTimeAuth.hs new file mode 100644 index 00000000..c703adbe --- /dev/null +++ b/src/Crypto/Saltine/Internal/OneTimeAuth.hs @@ -0,0 +1,104 @@ +{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving, DeriveGeneric, ForeignFunctionInterface #-} +-- | +-- Module : Crypto.Saltine.Internal.OneTimeAuth +-- Copyright : (c) Max Amanshauser 2021 +-- License : MIT +-- +-- Maintainer : max@lambdalifting.org +-- Stability : experimental +-- Portability : non-portable +-- +module Crypto.Saltine.Internal.OneTimeAuth ( + onetimeauth_bytes + , onetimeauth_keybytes + , c_onetimeauth + , c_onetimeauth_verify + , Key(..) + , Authenticator(..) +) where + + +import Control.DeepSeq +import Crypto.Saltine.Class +import Crypto.Saltine.Core.Hash (shorthash) +import Crypto.Saltine.Internal.Hash (nullShKey) +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) +import Data.Data (Data, Typeable) +import Data.Hashable (Hashable) +import Data.Monoid +import Foreign.C +import Foreign.Ptr +import GHC.Generics (Generic) + +import qualified Data.ByteString as S + +-- | An opaque 'auth' cryptographic key. +newtype Key = Key { unKey :: ByteString } deriving (Ord, Hashable, Data, Typeable, Generic, NFData) +instance Eq Key where + Key a == Key b = U.compare a b +instance Show Key where + show k = "OneTimeAuth.Key {hashesTo = \"" <> (bin2hex . shorthash nullShKey $ encode k) <> "\"}" + +instance IsEncoding Key where + decode v = if S.length v == onetimeauth_keybytes + then Just (Key v) + else Nothing + {-# INLINE decode #-} + encode (Key v) = v + {-# INLINE encode #-} + +-- | An opaque 'auth' authenticator. +newtype Authenticator = Au { unAu :: ByteString } deriving (Eq, Ord, Hashable, Data, Typeable, Generic, NFData) +instance Show Authenticator where + show k = "OneTimeAuth.Authenticator " <> bin2hex (encode k) + +instance IsEncoding Authenticator where + decode v = if S.length v == onetimeauth_bytes + then Just (Au v) + else Nothing + {-# INLINE decode #-} + encode (Au v) = v + {-# INLINE encode #-} + + +onetimeauth_bytes, onetimeauth_keybytes :: Int + +-- OneTimeAuth +-- | Size of a @crypto_onetimeauth@ authenticator. +onetimeauth_bytes = fromIntegral c_crypto_onetimeauth_bytes +-- | Size of a @crypto_onetimeauth@ authenticator key. +onetimeauth_keybytes = fromIntegral c_crypto_onetimeauth_keybytes + +-- src/libsodium/crypto_onetimeauth/crypto_onetimeauth.c +foreign import ccall "crypto_onetimeauth_bytes" + c_crypto_onetimeauth_bytes :: CSize +foreign import ccall "crypto_onetimeauth_keybytes" + c_crypto_onetimeauth_keybytes :: CSize + + +foreign import ccall "crypto_onetimeauth" + c_onetimeauth :: Ptr CChar + -- ^ Authenticator output buffer + -> Ptr CChar + -- ^ Constant message buffer + -> CULLong + -- ^ Length of message buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt + -- ^ Always 0 + +-- | We don't even include this in the IO monad since all of the +-- buffers are constant. +foreign import ccall "crypto_onetimeauth_verify" + c_onetimeauth_verify :: Ptr CChar + -- ^ Constant authenticator buffer + -> Ptr CChar + -- ^ Constant message buffer + -> CULLong + -- ^ Length of message buffer + -> Ptr CChar + -- ^ Constant key buffer + -> CInt + -- ^ Success if 0, failure if -1 diff --git a/src/Crypto/Saltine/Internal/Password.hs b/src/Crypto/Saltine/Internal/Password.hs new file mode 100644 index 00000000..26c5e52b --- /dev/null +++ b/src/Crypto/Saltine/Internal/Password.hs @@ -0,0 +1,567 @@ +{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving, DeriveGeneric, ForeignFunctionInterface #-} + +-- | +-- Module : Crypto.Saltine.Internal.Password +-- Copyright : (c) Promethea Raschke 2018 +-- Max Amanshauser 2021 +-- License : MIT +-- Maintainer : max@lambdalifting.org +-- Stability : experimental +-- Portability : non-portable + +module Crypto.Saltine.Internal.Password + ( c_pwhash + , c_pwhash_str + , c_pwhash_str_verify + , c_pwhash_str_needs_rehash + + , pwhash_alg_argon2i13 + , pwhash_alg_argon2id13 + , pwhash_alg_default + , algorithm + + -- Default algorithm constants + , pwhash_bytes_max + , pwhash_bytes_min + + , pwhash_memlimit_interactive + , pwhash_memlimit_moderate + , pwhash_memlimit_sensitive + , pwhash_memlimit_min + , pwhash_memlimit_max + + , pwhash_opslimit_interactive + , pwhash_opslimit_moderate + , pwhash_opslimit_sensitive + , pwhash_opslimit_min + , pwhash_opslimit_max + + , pwhash_passwd_min + , pwhash_passwd_max + , pwhash_saltbytes + , pwhash_strbytes + , pwhash_strprefix + + -- Argon2i algorithm constants + , pwhash_argon2i_bytes_max + , pwhash_argon2i_bytes_min + + , pwhash_argon2i_memlimit_interactive + , pwhash_argon2i_memlimit_moderate + , pwhash_argon2i_memlimit_sensitive + , pwhash_argon2i_memlimit_min + , pwhash_argon2i_memlimit_max + + , pwhash_argon2i_opslimit_interactive + , pwhash_argon2i_opslimit_moderate + , pwhash_argon2i_opslimit_sensitive + , pwhash_argon2i_opslimit_min + , pwhash_argon2i_opslimit_max + + , pwhash_argon2i_passwd_min + , pwhash_argon2i_passwd_max + , pwhash_argon2i_saltbytes + , pwhash_argon2i_strbytes + , pwhash_argon2i_strprefix + + -- Argon2id algorithm constants + , pwhash_argon2id_bytes_max + , pwhash_argon2id_bytes_min + + , pwhash_argon2id_memlimit_interactive + , pwhash_argon2id_memlimit_moderate + , pwhash_argon2id_memlimit_sensitive + , pwhash_argon2id_memlimit_min + , pwhash_argon2id_memlimit_max + + , pwhash_argon2id_opslimit_interactive + , pwhash_argon2id_opslimit_moderate + , pwhash_argon2id_opslimit_sensitive + , pwhash_argon2id_opslimit_min + , pwhash_argon2id_opslimit_max + + , pwhash_argon2id_passwd_min + , pwhash_argon2id_passwd_max + , pwhash_argon2id_saltbytes + , pwhash_argon2id_strbytes + , pwhash_argon2id_strprefix + + , Salt(..) + , PasswordHash(..) + , Opslimit(..) + , Memlimit(..) + , Policy(..) + , Algorithm(..) + ) where + +import Control.DeepSeq +import Crypto.Saltine.Class +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) +import Data.Data (Data, Typeable) +import Data.Hashable (Hashable) +import Data.Monoid +import Data.Text (Text) +import GHC.Generics (Generic) +import Foreign.C +import Foreign.Ptr + +import qualified Data.ByteString as S +import qualified Data.Text.Encoding as DTE + + +-- | Salt for deriving keys from passwords +newtype Salt = Salt { unSalt :: ByteString } deriving (Ord, Data, Hashable, Typeable, Generic, NFData) +instance Eq Salt where + Salt a == Salt b = U.compare a b +instance Show Salt where + show k = "Password.Salt " <> bin2hex (encode k) + +instance IsEncoding Salt where + decode v = if S.length v == pwhash_saltbytes + then Just (Salt v) + else Nothing + {-# INLINE decode #-} + encode (Salt v) = v + {-# INLINE encode #-} + +-- | Verification string for stored passwords +-- This hash contains only printable characters, hence we can just derive Show. +newtype PasswordHash = PasswordHash { unPasswordHash :: Text } deriving (Ord, Data, Hashable, Typeable, Generic, Show, NFData) +-- Constant time Eq instance, just in case. +instance Eq PasswordHash where + PasswordHash a == PasswordHash b = U.compare (DTE.encodeUtf8 a) (DTE.encodeUtf8 b) + +-- | Wrapper type for the operations used by password hashing +newtype Opslimit = Opslimit { getOpslimit :: Int } deriving (Eq, Ord, Data, Hashable, Typeable, Generic, Show, NFData) + +-- | Wrapper type for the memory used by password hashing +newtype Memlimit = Memlimit { getMemlimit :: Int } deriving (Eq, Ord, Data, Hashable, Typeable, Generic, Show, NFData) + +-- | Wrapper for opslimit, memlimit and algorithm +data Policy = Policy + { opsPolicy :: Opslimit + , memPolicy :: Memlimit + , algPolicy :: Algorithm + } deriving (Eq, Ord, Data, Typeable, Generic, Show) +instance Hashable Policy + +-- | Algorithms known to Libsodium, as an enum datatype +data Algorithm + = DefaultAlgorithm + | Argon2i13 + | Argon2id13 + deriving (Eq, Enum, Ord, Show, Generic, Data, Typeable, Bounded) +instance Hashable Algorithm + +algorithm :: Algorithm -> CInt +algorithm DefaultAlgorithm = fromIntegral pwhash_alg_default +algorithm Argon2i13 = fromIntegral pwhash_alg_argon2i13 +algorithm Argon2id13 = fromIntegral pwhash_alg_argon2id13 + +-- | Lets libsodium pick a hashing algorithm +pwhash_alg_default :: Int +pwhash_alg_default = fromIntegral c_crypto_pwhash_alg_default +-- | version 1.3 of the Argon2i algorithm +pwhash_alg_argon2i13 :: Int +pwhash_alg_argon2i13 = fromIntegral c_crypto_pwhash_alg_argon2i13 +-- | version 1.3 of the Argon2id algorithm +pwhash_alg_argon2id13 :: Int +pwhash_alg_argon2id13 = fromIntegral c_crypto_pwhash_alg_argon2id13 + +-- | Constants for the default algorithm +-- | Minimum output length for key derivation (16 (128 bits)). +pwhash_bytes_min :: Int +pwhash_bytes_min = fromIntegral c_crypto_pwhash_bytes_min +-- | Maximum output length for key derivation. +pwhash_bytes_max :: Int +pwhash_bytes_max = fromIntegral c_crypto_pwhash_bytes_max + +-- | Minimum allowed memory limit for password hashing +pwhash_memlimit_min :: Int +pwhash_memlimit_min = fromIntegral c_crypto_pwhash_memlimit_min +-- | Maximum allowed memory limit for password hashing +pwhash_memlimit_max :: Int +pwhash_memlimit_max = fromIntegral c_crypto_pwhash_memlimit_max +-- | Constant for currently 64MB memory +pwhash_memlimit_interactive :: Int +pwhash_memlimit_interactive = fromIntegral c_crypto_pwhash_memlimit_interactive +-- | Constant for currently 256MB memory +pwhash_memlimit_moderate :: Int +pwhash_memlimit_moderate = fromIntegral c_crypto_pwhash_memlimit_moderate +-- | Constant for currently 1024MB memory +pwhash_memlimit_sensitive :: Int +pwhash_memlimit_sensitive = fromIntegral c_crypto_pwhash_memlimit_sensitive + +-- | Minimum allowed number of computations for password hashing +pwhash_opslimit_min :: Int +pwhash_opslimit_min = fromIntegral c_crypto_pwhash_opslimit_min +-- | Maximum allowed number of computations for password hashing +pwhash_opslimit_max :: Int +pwhash_opslimit_max = fromIntegral c_crypto_pwhash_opslimit_max + +-- | Constant for relatively fast hashing +pwhash_opslimit_interactive :: Int +pwhash_opslimit_interactive = fromIntegral c_crypto_pwhash_opslimit_interactive +-- | Constant for moderately fast hashing +pwhash_opslimit_moderate :: Int +pwhash_opslimit_moderate = fromIntegral c_crypto_pwhash_opslimit_moderate +-- | Constant for relatively slow hashing +pwhash_opslimit_sensitive :: Int +pwhash_opslimit_sensitive = fromIntegral c_crypto_pwhash_opslimit_sensitive + +-- | Minimum number of characters in password for key derivation +pwhash_passwd_min :: Int +pwhash_passwd_min = fromIntegral c_crypto_pwhash_passwd_min +-- | Maximum number of characters in password for key derivation +pwhash_passwd_max :: Int +pwhash_passwd_max = fromIntegral c_crypto_pwhash_passwd_max + +-- | Size of salt +pwhash_saltbytes :: Int +pwhash_saltbytes = fromIntegral c_crypto_pwhash_saltbytes +-- | (Maximum) size of password hashing output +pwhash_strbytes :: Int +pwhash_strbytes = fromIntegral c_crypto_pwhash_strbytes +-- string that hashes with this algorithm are prefixed with +pwhash_strprefix :: Int +pwhash_strprefix = fromIntegral c_crypto_pwhash_strprefix + + +-- | Constants for Argon2ID +-- | Minimum output length for key derivation (= 16 (128 bits)). +pwhash_argon2id_bytes_min :: Int +pwhash_argon2id_bytes_min = fromIntegral c_crypto_pwhash_argon2id_bytes_min +-- | Maximum output length for key derivation. +pwhash_argon2id_bytes_max :: Int +pwhash_argon2id_bytes_max = fromIntegral c_crypto_pwhash_argon2id_bytes_max + +-- | Minimum allowed memory limit for password hashing +pwhash_argon2id_memlimit_min :: Int +pwhash_argon2id_memlimit_min = fromIntegral c_crypto_pwhash_argon2id_memlimit_min +-- | Maximum allowed memory limit for password hashing +pwhash_argon2id_memlimit_max :: Int +pwhash_argon2id_memlimit_max = fromIntegral c_crypto_pwhash_argon2id_memlimit_max +-- | Constant for currently 64MB memory +pwhash_argon2id_memlimit_interactive :: Int +pwhash_argon2id_memlimit_interactive = fromIntegral c_crypto_pwhash_argon2id_memlimit_interactive +-- | Constant for currently 256MB memory +pwhash_argon2id_memlimit_moderate :: Int +pwhash_argon2id_memlimit_moderate = fromIntegral c_crypto_pwhash_argon2id_memlimit_moderate +-- | Constant for currently 1024MB memory +pwhash_argon2id_memlimit_sensitive :: Int +pwhash_argon2id_memlimit_sensitive = fromIntegral c_crypto_pwhash_argon2id_memlimit_sensitive + +-- | Minimum allowed number of computations for password hashing +pwhash_argon2id_opslimit_min :: Int +pwhash_argon2id_opslimit_min = fromIntegral c_crypto_pwhash_argon2id_opslimit_min +-- | Maximum allowed number of computations for password hashing +pwhash_argon2id_opslimit_max :: Int +pwhash_argon2id_opslimit_max = fromIntegral c_crypto_pwhash_argon2id_opslimit_max + +-- | Constant for relatively fast hashing +pwhash_argon2id_opslimit_interactive :: Int +pwhash_argon2id_opslimit_interactive = fromIntegral c_crypto_pwhash_argon2id_opslimit_interactive +-- | Constant for moderately fast hashing +pwhash_argon2id_opslimit_moderate :: Int +pwhash_argon2id_opslimit_moderate = fromIntegral c_crypto_pwhash_argon2id_opslimit_moderate +-- | Constant for relatively slow hashing +pwhash_argon2id_opslimit_sensitive :: Int +pwhash_argon2id_opslimit_sensitive = fromIntegral c_crypto_pwhash_argon2id_opslimit_sensitive + +-- | Minimum number of characters in password for key derivation +pwhash_argon2id_passwd_min :: Int +pwhash_argon2id_passwd_min = fromIntegral c_crypto_pwhash_argon2id_passwd_min +-- | Maximum number of characters in password for key derivation +pwhash_argon2id_passwd_max :: Int +pwhash_argon2id_passwd_max = fromIntegral c_crypto_pwhash_argon2id_passwd_max + +-- | Size of salt +pwhash_argon2id_saltbytes :: Int +pwhash_argon2id_saltbytes = fromIntegral c_crypto_pwhash_argon2id_saltbytes +-- | (Maximum) size of password hashing output +pwhash_argon2id_strbytes :: Int +pwhash_argon2id_strbytes = fromIntegral c_crypto_pwhash_argon2id_strbytes +-- string that hashes with this algorithm are prefixed with +pwhash_argon2id_strprefix :: Int +pwhash_argon2id_strprefix = fromIntegral c_crypto_pwhash_argon2id_strprefix + +-- | Constants for ARGON2I +-- | Minimum output length for key derivation (= 16 (128 bits)). +pwhash_argon2i_bytes_min :: Int +pwhash_argon2i_bytes_min = fromIntegral c_crypto_pwhash_argon2i_bytes_min +-- | Maximum output length for key derivation. +pwhash_argon2i_bytes_max :: Int +pwhash_argon2i_bytes_max = fromIntegral c_crypto_pwhash_argon2i_bytes_max + +-- | Minimum allowed memory limit for password hashing +pwhash_argon2i_memlimit_min :: Int +pwhash_argon2i_memlimit_min = fromIntegral c_crypto_pwhash_argon2i_memlimit_min +-- | Maximum allowed memory limit for password hashing +pwhash_argon2i_memlimit_max :: Int +pwhash_argon2i_memlimit_max = fromIntegral c_crypto_pwhash_argon2i_memlimit_max +-- | Constant for currently 64MB memory +pwhash_argon2i_memlimit_interactive :: Int +pwhash_argon2i_memlimit_interactive = fromIntegral c_crypto_pwhash_argon2i_memlimit_interactive +-- | Constant for currently 256MB memory +pwhash_argon2i_memlimit_moderate :: Int +pwhash_argon2i_memlimit_moderate = fromIntegral c_crypto_pwhash_argon2i_memlimit_moderate +-- | Constant for currently 1024MB memory +pwhash_argon2i_memlimit_sensitive :: Int +pwhash_argon2i_memlimit_sensitive = fromIntegral c_crypto_pwhash_argon2i_memlimit_sensitive + +-- | Minimum allowed number of computations for password hashing +pwhash_argon2i_opslimit_min :: Int +pwhash_argon2i_opslimit_min = fromIntegral c_crypto_pwhash_argon2i_opslimit_min +-- | Maximum allowed number of computations for password hashing +pwhash_argon2i_opslimit_max :: Int +pwhash_argon2i_opslimit_max = fromIntegral c_crypto_pwhash_argon2i_opslimit_max + +-- | Constant for relatively fast hashing +pwhash_argon2i_opslimit_interactive :: Int +pwhash_argon2i_opslimit_interactive = fromIntegral c_crypto_pwhash_argon2i_opslimit_interactive +-- | Constant for moderately fast hashing +pwhash_argon2i_opslimit_moderate :: Int +pwhash_argon2i_opslimit_moderate = fromIntegral c_crypto_pwhash_argon2i_opslimit_moderate +-- | Constant for relatively slow hashing +pwhash_argon2i_opslimit_sensitive :: Int +pwhash_argon2i_opslimit_sensitive = fromIntegral c_crypto_pwhash_argon2i_opslimit_sensitive + +-- | Minimum number of characters in password for key derivation +pwhash_argon2i_passwd_min :: Int +pwhash_argon2i_passwd_min = fromIntegral c_crypto_pwhash_argon2i_passwd_min +-- | Maximum number of characters in password for key derivation +pwhash_argon2i_passwd_max :: Int +pwhash_argon2i_passwd_max = fromIntegral c_crypto_pwhash_argon2i_passwd_max + +-- | Size of salt +pwhash_argon2i_saltbytes :: Int +pwhash_argon2i_saltbytes = fromIntegral c_crypto_pwhash_argon2i_saltbytes +-- | (Maximum) size of password hashing output +pwhash_argon2i_strbytes :: Int +pwhash_argon2i_strbytes = fromIntegral c_crypto_pwhash_argon2i_strbytes +-- string that hashes with this algorithm are prefixed with +pwhash_argon2i_strprefix :: Int +pwhash_argon2i_strprefix = fromIntegral c_crypto_pwhash_argon2i_strprefix + + + +foreign import ccall "crypto_pwhash" + c_pwhash + :: Ptr CChar + -- ^ Derived key output buffer + -> CULLong + -- ^ Derived key length + -> Ptr CChar + -- ^ Password input buffer + -> CULLong + -- ^ Password length + -> Ptr CChar + -- ^ Salt input buffer + -> CULLong + -- ^ Operation limit + -> CSize + -- ^ Memory usage limit + -> CInt + -- ^ Algorithm + -> IO CInt + +foreign import ccall "crypto_pwhash_str" + c_pwhash_str + :: Ptr CChar + -- ^ Hashed password output buffer + -> Ptr CChar + -- ^ Password input buffer + -> CULLong + -- ^ Password length + -> CULLong + -- ^ Operation limit + -> CSize + -- ^ Memory usage limit + -> IO CInt + +foreign import ccall "crypto_pwhash_str_verify" + c_pwhash_str_verify + :: Ptr CChar + -- ^ Hashed password input buffer + -> Ptr CChar + -- ^ Password input buffer + -> CULLong + -- ^ Password length + -> IO CInt + +foreign import ccall "crypto_pwhash_str_needs_rehash" + c_pwhash_str_needs_rehash + :: Ptr CChar + -- ^ Hashed password input buffer + -> CULLong + -- ^ Operation limit + -> CSize + -- ^ Memory usage limit + -> IO CInt + +foreign import ccall "crypto_pwhash_alg_argon2id13" + c_crypto_pwhash_alg_argon2id13 :: CSize + +foreign import ccall "crypto_pwhash_alg_argon2i13" + c_crypto_pwhash_alg_argon2i13 :: CSize + +foreign import ccall "crypto_pwhash_alg_default" + c_crypto_pwhash_alg_default :: CSize + +-- Constants for the default algorithm +foreign import ccall "crypto_pwhash_bytes_min" + c_crypto_pwhash_bytes_min :: CSize + +foreign import ccall "crypto_pwhash_bytes_max" + c_crypto_pwhash_bytes_max :: CSize + +foreign import ccall "crypto_pwhash_memlimit_min" + c_crypto_pwhash_memlimit_min :: CSize + +foreign import ccall "crypto_pwhash_memlimit_max" + c_crypto_pwhash_memlimit_max :: CSize + +foreign import ccall "crypto_pwhash_memlimit_interactive" + c_crypto_pwhash_memlimit_interactive :: CSize + +foreign import ccall "crypto_pwhash_memlimit_moderate" + c_crypto_pwhash_memlimit_moderate :: CSize + +foreign import ccall "crypto_pwhash_memlimit_sensitive" + c_crypto_pwhash_memlimit_sensitive :: CSize + +foreign import ccall "crypto_pwhash_opslimit_min" + c_crypto_pwhash_opslimit_min :: CSize + +foreign import ccall "crypto_pwhash_opslimit_max" + c_crypto_pwhash_opslimit_max :: CSize + +foreign import ccall "crypto_pwhash_opslimit_interactive" + c_crypto_pwhash_opslimit_interactive :: CSize + +foreign import ccall "crypto_pwhash_opslimit_moderate" + c_crypto_pwhash_opslimit_moderate :: CSize + +foreign import ccall "crypto_pwhash_opslimit_sensitive" + c_crypto_pwhash_opslimit_sensitive :: CSize + +foreign import ccall "crypto_pwhash_passwd_min" + c_crypto_pwhash_passwd_min :: CSize + +foreign import ccall "crypto_pwhash_passwd_max" + c_crypto_pwhash_passwd_max :: CSize + +foreign import ccall "crypto_pwhash_saltbytes" + c_crypto_pwhash_saltbytes :: CSize + +foreign import ccall "crypto_pwhash_strbytes" + c_crypto_pwhash_strbytes :: CSize + +foreign import ccall "crypto_pwhash_strprefix" + c_crypto_pwhash_strprefix :: CSize + +-- Constants for ARGON2ID (currently default) +foreign import ccall "crypto_pwhash_argon2id_bytes_min" + c_crypto_pwhash_argon2id_bytes_min :: CSize + +foreign import ccall "crypto_pwhash_argon2id_bytes_max" + c_crypto_pwhash_argon2id_bytes_max :: CSize + +foreign import ccall "crypto_pwhash_argon2id_memlimit_min" + c_crypto_pwhash_argon2id_memlimit_min :: CSize + +foreign import ccall "crypto_pwhash_argon2id_memlimit_max" + c_crypto_pwhash_argon2id_memlimit_max :: CSize + +foreign import ccall "crypto_pwhash_argon2id_memlimit_interactive" + c_crypto_pwhash_argon2id_memlimit_interactive :: CSize + +foreign import ccall "crypto_pwhash_argon2id_memlimit_moderate" + c_crypto_pwhash_argon2id_memlimit_moderate :: CSize + +foreign import ccall "crypto_pwhash_argon2id_memlimit_sensitive" + c_crypto_pwhash_argon2id_memlimit_sensitive :: CSize + +foreign import ccall "crypto_pwhash_argon2id_opslimit_min" + c_crypto_pwhash_argon2id_opslimit_min :: CSize + +foreign import ccall "crypto_pwhash_argon2id_opslimit_max" + c_crypto_pwhash_argon2id_opslimit_max :: CSize + +foreign import ccall "crypto_pwhash_argon2id_opslimit_interactive" + c_crypto_pwhash_argon2id_opslimit_interactive :: CSize + +foreign import ccall "crypto_pwhash_argon2id_opslimit_moderate" + c_crypto_pwhash_argon2id_opslimit_moderate :: CSize + +foreign import ccall "crypto_pwhash_argon2id_opslimit_sensitive" + c_crypto_pwhash_argon2id_opslimit_sensitive :: CSize + +foreign import ccall "crypto_pwhash_argon2id_passwd_min" + c_crypto_pwhash_argon2id_passwd_min :: CSize + +foreign import ccall "crypto_pwhash_argon2id_passwd_max" + c_crypto_pwhash_argon2id_passwd_max :: CSize + +foreign import ccall "crypto_pwhash_argon2id_saltbytes" + c_crypto_pwhash_argon2id_saltbytes :: CSize + +foreign import ccall "crypto_pwhash_argon2id_strbytes" + c_crypto_pwhash_argon2id_strbytes :: CSize + +foreign import ccall "crypto_pwhash_argon2id_strprefix" + c_crypto_pwhash_argon2id_strprefix :: CSize + + +-- Constants for ARGON2I +foreign import ccall "crypto_pwhash_argon2i_bytes_min" + c_crypto_pwhash_argon2i_bytes_min :: CSize + +foreign import ccall "crypto_pwhash_argon2i_bytes_max" + c_crypto_pwhash_argon2i_bytes_max :: CSize + +foreign import ccall "crypto_pwhash_argon2i_memlimit_min" + c_crypto_pwhash_argon2i_memlimit_min :: CSize + +foreign import ccall "crypto_pwhash_argon2i_memlimit_max" + c_crypto_pwhash_argon2i_memlimit_max :: CSize + +foreign import ccall "crypto_pwhash_argon2i_memlimit_interactive" + c_crypto_pwhash_argon2i_memlimit_interactive :: CSize + +foreign import ccall "crypto_pwhash_argon2i_memlimit_moderate" + c_crypto_pwhash_argon2i_memlimit_moderate :: CSize + +foreign import ccall "crypto_pwhash_argon2i_memlimit_sensitive" + c_crypto_pwhash_argon2i_memlimit_sensitive :: CSize + +foreign import ccall "crypto_pwhash_argon2i_opslimit_min" + c_crypto_pwhash_argon2i_opslimit_min :: CSize + +foreign import ccall "crypto_pwhash_argon2i_opslimit_max" + c_crypto_pwhash_argon2i_opslimit_max :: CSize + +foreign import ccall "crypto_pwhash_argon2i_opslimit_interactive" + c_crypto_pwhash_argon2i_opslimit_interactive :: CSize + +foreign import ccall "crypto_pwhash_argon2i_opslimit_moderate" + c_crypto_pwhash_argon2i_opslimit_moderate :: CSize + +foreign import ccall "crypto_pwhash_argon2i_opslimit_sensitive" + c_crypto_pwhash_argon2i_opslimit_sensitive :: CSize + +foreign import ccall "crypto_pwhash_argon2i_passwd_min" + c_crypto_pwhash_argon2i_passwd_min :: CSize + +foreign import ccall "crypto_pwhash_argon2i_passwd_max" + c_crypto_pwhash_argon2i_passwd_max :: CSize + +foreign import ccall "crypto_pwhash_argon2i_saltbytes" + c_crypto_pwhash_argon2i_saltbytes :: CSize + +foreign import ccall "crypto_pwhash_argon2i_strbytes" + c_crypto_pwhash_argon2i_strbytes :: CSize + +foreign import ccall "crypto_pwhash_argon2i_strprefix" + c_crypto_pwhash_argon2i_strprefix :: CSize diff --git a/src/Crypto/Saltine/Internal/ScalarMult.hs b/src/Crypto/Saltine/Internal/ScalarMult.hs new file mode 100644 index 00000000..03a084bf --- /dev/null +++ b/src/Crypto/Saltine/Internal/ScalarMult.hs @@ -0,0 +1,91 @@ +{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving, DeriveGeneric, ForeignFunctionInterface #-} +-- | +-- Module : Crypto.Saltine.Internal.ScalarMult +-- Copyright : (c) Max Amanshauser 2021 +-- License : MIT +-- +-- Maintainer : max@lambdalifting.org +-- Stability : experimental +-- Portability : non-portable +-- +module Crypto.Saltine.Internal.ScalarMult ( + scalarmult_bytes + , scalarmult_scalarbytes + , c_scalarmult + , c_scalarmult_base + , GroupElement(..) + , Scalar(..) +) where + +import Control.DeepSeq +import Crypto.Saltine.Class +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) +import Data.Data (Data, Typeable) +import Data.Hashable (Hashable) +import Foreign.C +import Foreign.Ptr +import GHC.Generics (Generic) + +import qualified Data.ByteString as S + + +-- | A group element. +newtype GroupElement = GE { unGE :: ByteString } deriving (Eq, Ord, Hashable, Data, Typeable, Generic, NFData) +instance Show GroupElement where + show = bin2hex . encode + +instance IsEncoding GroupElement where + decode v = if S.length v == scalarmult_bytes + then Just (GE v) + else Nothing + {-# INLINE decode #-} + encode (GE v) = v + {-# INLINE encode #-} + +-- | A scalar integer. +newtype Scalar = Sc { unSc :: ByteString } deriving (Eq, Ord, Hashable, Data, Typeable, Generic, NFData) +instance Show Scalar where + show = bin2hex . encode + +instance IsEncoding Scalar where + decode v = if S.length v == scalarmult_scalarbytes + then Just (Sc v) + else Nothing + {-# INLINE decode #-} + encode (Sc v) = v + {-# INLINE encode #-} + + +scalarmult_bytes, scalarmult_scalarbytes :: Int + +-- ScalarMult +-- | Size of a group element string representation for +-- @crypto_scalarmult@. +scalarmult_bytes = fromIntegral c_crypto_scalarmult_bytes +-- | Size of a integer string representation for @crypto_scalarmult@. +scalarmult_scalarbytes = fromIntegral c_crypto_scalarmult_scalarbytes + +-- src/libsodium/crypto_scalarmult/crypto_scalarmult.c +foreign import ccall "crypto_scalarmult_bytes" + c_crypto_scalarmult_bytes :: CSize +foreign import ccall "crypto_scalarmult_scalarbytes" + c_crypto_scalarmult_scalarbytes :: CSize + +foreign import ccall "crypto_scalarmult" + c_scalarmult :: Ptr CChar + -- ^ Output group element buffer + -> Ptr CChar + -- ^ Input integer buffer + -> Ptr CChar + -- ^ Input group element buffer + -> IO CInt + -- ^ Always 0 + +foreign import ccall "crypto_scalarmult_base" + c_scalarmult_base :: Ptr CChar + -- ^ Output group element buffer + -> Ptr CChar + -- ^ Input integer buffer + -> IO CInt + -- ^ Always 0 diff --git a/src/Crypto/Saltine/Internal/SecretBox.hs b/src/Crypto/Saltine/Internal/SecretBox.hs new file mode 100644 index 00000000..a970eac4 --- /dev/null +++ b/src/Crypto/Saltine/Internal/SecretBox.hs @@ -0,0 +1,179 @@ +{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving, DeriveGeneric, ForeignFunctionInterface #-} +-- | +-- Module : Crypto.Saltine.Internal.SecretBox +-- Copyright : (c) Max Amanshauser 2021 +-- License : MIT +-- +-- Maintainer : max@lambdalifting.org +-- Stability : experimental +-- Portability : non-portable +-- +module Crypto.Saltine.Internal.SecretBox ( + secretbox_keybytes, + secretbox_noncebytes, + secretbox_macbytes, + secretbox_zerobytes, + secretbox_boxzerobytes, + c_secretbox, + c_secretbox_detached, + c_secretbox_open, + c_secretbox_open_detached, + Key(..), + Nonce(..), + Authenticator(..) +) where + +import Control.DeepSeq (NFData) +import Crypto.Saltine.Class +import Crypto.Saltine.Core.Hash (shorthash) +import Crypto.Saltine.Internal.Hash (nullShKey) +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) +import Data.Data (Data, Typeable) +import Data.Hashable (Hashable) +import Data.Monoid +import Foreign.C +import Foreign.Ptr +import GHC.Generics (Generic) + +import qualified Data.ByteString as S + +secretbox_keybytes, secretbox_noncebytes, secretbox_macbytes, secretbox_zerobytes, secretbox_boxzerobytes :: Int + +-- | An opaque 'secretbox' cryptographic key. +newtype Key = Key { unKey :: ByteString } deriving (Ord, Hashable, Data, Typeable, Generic, NFData) +instance Eq Key where + Key a == Key b = U.compare a b +instance Show Key where + show k = "SecretBox.Key {hashesTo = \"" <> (bin2hex . shorthash nullShKey $ encode k) <> "\"}" + +instance IsEncoding Key where + decode v = if S.length v == secretbox_keybytes + then Just (Key v) + else Nothing + {-# INLINE decode #-} + encode (Key v) = v + {-# INLINE encode #-} + +-- | An opaque 'secretbox' nonce. +newtype Nonce = Nonce { unNonce :: ByteString } deriving (Eq, Ord, Hashable, Data, Typeable, Generic, NFData) +instance Show Nonce where + show k = "SecretBox.Nonce " <> bin2hex (encode k) + +instance IsEncoding Nonce where + decode v = if S.length v == secretbox_noncebytes + then Just (Nonce v) + else Nothing + {-# INLINE decode #-} + encode (Nonce v) = v + {-# INLINE encode #-} + +instance IsNonce Nonce where + zero = Nonce (S.replicate secretbox_noncebytes 0) + nudge (Nonce n) = Nonce (nudgeBS n) + + +-- | An Authenticator for a Message +newtype Authenticator = Au { unAu :: ByteString } deriving (Eq, Ord, Data, Typeable, Hashable, Generic, NFData) +instance Show Authenticator where + show k = "Sign.Authenticator " <> bin2hex (encode k) + +instance IsEncoding Authenticator where + decode v = if S.length v == secretbox_macbytes + then Just (Au v) + else Nothing + {-# INLINE decode #-} + encode (Au v) = v + {-# INLINE encode #-} + + +-- | Size of a @crypto_secretbox@ secret key +secretbox_keybytes = fromIntegral c_crypto_secretbox_keybytes +-- | Size of a @crypto_secretbox@ nonce +secretbox_noncebytes = fromIntegral c_crypto_secretbox_noncebytes +-- | Size of a @crypto_secretbox@ mac +secretbox_macbytes = fromIntegral c_crypto_secretbox_macbytes +-- | Size of 0-padding prepended to messages before using +-- @crypto_secretbox@ or after using @crypto_secretbox_open@ +secretbox_zerobytes = fromIntegral c_crypto_secretbox_zerobytes +-- | Size of 0-padding prepended to ciphertext before using +-- @crypto_secretbox_open@ or after using @crypto_secretbox@ +secretbox_boxzerobytes = fromIntegral c_crypto_secretbox_boxzerobytes + +-- src/libsodium/crypto_secretbox/crypto_secretbox.c +foreign import ccall "crypto_secretbox_keybytes" + c_crypto_secretbox_keybytes :: CSize +foreign import ccall "crypto_secretbox_noncebytes" + c_crypto_secretbox_noncebytes :: CSize +foreign import ccall "crypto_secretbox_macbytes" + c_crypto_secretbox_macbytes :: CSize +foreign import ccall "crypto_secretbox_zerobytes" + c_crypto_secretbox_zerobytes :: CSize +foreign import ccall "crypto_secretbox_boxzerobytes" + c_crypto_secretbox_boxzerobytes :: CSize + +-- | The secretbox C API uses 0-padded C strings. Always returns 0. +foreign import ccall "crypto_secretbox" + c_secretbox + :: Ptr CChar + -- ^ Cipher 0-padded output buffer + -> Ptr CChar + -- ^ Constant 0-padded message input buffer + -> CULLong + -- ^ Length of message input buffer (incl. 0s) + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt + +-- | The secretbox_detached C API uses C strings. Always returns 0. +foreign import ccall "crypto_secretbox_detached" + c_secretbox_detached + :: Ptr CChar + -- ^ Ciphertext output buffer + -> Ptr CChar + -- ^ Authentication tag output buffer + -> Ptr CChar + -- ^ Constant message input buffer + -> CULLong + -- ^ Length of message input buffer (incl. 0s) + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt + +-- | The secretbox C API uses 0-padded C strings. Returns 0 if +-- successful or -1 if verification failed. +foreign import ccall "crypto_secretbox_open" + c_secretbox_open + :: Ptr CChar + -- ^ Message 0-padded output buffer + -> Ptr CChar + -- ^ Constant 0-padded message input buffer + -> CULLong + -- ^ Length of message input buffer (incl. 0s) + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt + +-- | The secretbox C API uses C strings. Returns 0 if +-- successful or -1 if verification failed. +foreign import ccall "crypto_secretbox_open_detached" + c_secretbox_open_detached + :: Ptr CChar + -- ^ Message output buffer + -> Ptr CChar + -- ^ Constant ciphertext input buffer + -> Ptr CChar + -- ^ Constant auth tag input buffer + -> CULLong + -- ^ Length of ciphertext input buffer + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt diff --git a/src/Crypto/Saltine/Internal/Sign.hs b/src/Crypto/Saltine/Internal/Sign.hs new file mode 100644 index 00000000..69d93b88 --- /dev/null +++ b/src/Crypto/Saltine/Internal/Sign.hs @@ -0,0 +1,182 @@ +{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving, DeriveGeneric, ForeignFunctionInterface #-} +-- | +-- Module : Crypto.Saltine.Internal.Sign +-- Copyright : (c) Max Amanshauser 2021 +-- License : MIT +-- +-- Maintainer : max@lambdalifting.org +-- Stability : experimental +-- Portability : non-portable +-- +module Crypto.Saltine.Internal.Sign ( + sign_bytes + , sign_publickeybytes + , sign_secretkeybytes + , c_sign_keypair + , c_sign + , c_sign_open + , c_sign_detached + , c_sign_verify_detached + , SecretKey(..) + , PublicKey(..) + , Keypair(..) + , Signature(..) +) where + +import Control.DeepSeq (NFData) +import Crypto.Saltine.Class +import Crypto.Saltine.Core.Hash (shorthash) +import Crypto.Saltine.Internal.Hash (nullShKey) +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) +import Data.Data (Data, Typeable) +import Data.Hashable (Hashable) +import Data.Monoid +import Foreign.C +import Foreign.Ptr +import GHC.Generics (Generic) + +import qualified Data.ByteString as S + + +-- | An opaque 'box' cryptographic secret key. +newtype SecretKey = SK { unSK :: ByteString } deriving (Ord, Hashable, Data, Typeable, Generic, NFData) +instance Eq SecretKey where + SK a == SK b = U.compare a b +instance Show SecretKey where + show k = "Sign.SecretKey {hashesTo = \"" <> (bin2hex . shorthash nullShKey $ encode k) <> "\"}" + +instance IsEncoding SecretKey where + decode v = if S.length v == sign_secretkeybytes + then Just (SK v) + else Nothing + {-# INLINE decode #-} + encode (SK v) = v + {-# INLINE encode #-} + +-- | An opaque 'box' cryptographic public key. +newtype PublicKey = PK { unPK :: ByteString } deriving (Ord, Data, Typeable, Hashable, Generic, NFData) +instance Eq PublicKey where + PK a == PK b = U.compare a b +instance Show PublicKey where + show k = "Sign.PublicKey {hashesTo = \"" <> (bin2hex . shorthash nullShKey $ encode k) <> "\"}" + +instance IsEncoding PublicKey where + decode v = if S.length v == sign_publickeybytes + then Just (PK v) + else Nothing + {-# INLINE decode #-} + encode (PK v) = v + {-# INLINE encode #-} + +-- | A convenience type for keypairs +data Keypair = Keypair { + secretKey :: SecretKey + , publicKey :: PublicKey +} deriving (Ord, Data, Typeable, Generic) + +instance Eq Keypair where + kp1 == kp2 = U.compare (encode $ secretKey kp1) (encode $ secretKey kp2) + !&&! U.compare (encode $ publicKey kp1) (encode $ publicKey kp2) + +instance Show Keypair where + show k = "Sign.Keypair {secretKey = " <> show (secretKey k) <> ", publicKey = " <> show (publicKey k) <> "}" + +instance Hashable Keypair +instance NFData Keypair + + +-- | A signature for a Message +newtype Signature = Signature { unSignature :: ByteString } deriving (Ord, Data, Typeable, Hashable, Generic, NFData) +instance Eq Signature where + Signature a == Signature b = U.compare a b +instance Show Signature where + show k = "Sign.Signature " <> bin2hex (encode k) + +-- | Actual signatures may be shorter, but not when generated with saltine. +instance IsEncoding Signature where + decode v = if S.length v == sign_bytes + then Just (Signature v) + else Nothing + {-# INLINE decode #-} + encode (Signature s) = s + {-# INLINE encode #-} + +sign_bytes, sign_publickeybytes, sign_secretkeybytes :: Int + +-- Signatures +-- | The maximum size of a signature prepended to a message to form a +-- signed message. +sign_bytes = fromIntegral c_crypto_sign_bytes +-- | The size of a public key for signing verification +sign_publickeybytes = fromIntegral c_crypto_sign_publickeybytes +-- | The size of a secret key for signing +sign_secretkeybytes = fromIntegral c_crypto_sign_secretkeybytes + +-- src/libsodium/crypto_sign/crypto_sign.c +foreign import ccall "crypto_sign_bytes" + c_crypto_sign_bytes :: CSize +foreign import ccall "crypto_sign_publickeybytes" + c_crypto_sign_publickeybytes :: CSize +foreign import ccall "crypto_sign_secretkeybytes" + c_crypto_sign_secretkeybytes :: CSize + + +foreign import ccall "crypto_sign_keypair" + c_sign_keypair :: Ptr CChar + -- ^ Public key output buffer + -> Ptr CChar + -- ^ Secret key output buffer + -> IO CInt + -- ^ Always 0 + +foreign import ccall "crypto_sign" + c_sign :: Ptr CChar + -- ^ Signed message output buffer + -> Ptr CULLong + -- ^ Length of signed message + -> Ptr CChar + -- ^ Constant message buffer + -> CULLong + -- ^ Length of message input buffer + -> Ptr CChar + -- ^ Constant secret key buffer + -> IO CInt + -- ^ Always 0 + +foreign import ccall "crypto_sign_open" + c_sign_open :: Ptr CChar + -- ^ Message output buffer + -> Ptr CULLong + -- ^ Length of message + -> Ptr CChar + -- ^ Constant signed message buffer + -> CULLong + -- ^ Length of signed message buffer + -> Ptr CChar + -- ^ Public key buffer + -> IO CInt + -- ^ 0 if signature is verifiable, -1 otherwise + +foreign import ccall "crypto_sign_detached" + c_sign_detached :: Ptr CChar + -- ^ Signature output buffer + -> Ptr CULLong + -- ^ Length of the signature + -> Ptr CChar + -- ^ Constant message buffer + -> CULLong + -- ^ Length of message buffer + -> Ptr CChar + -- ^ Constant secret key buffer + -> IO CInt +foreign import ccall "crypto_sign_verify_detached" + c_sign_verify_detached :: Ptr CChar + -- ^ Signature buffer + -> Ptr CChar + -- ^ Constant signed message buffer + -> CULLong + -- ^ Length of signed message buffer + -> Ptr CChar + -- ^ Public key buffer + -> IO CInt diff --git a/src/Crypto/Saltine/Internal/Stream.hs b/src/Crypto/Saltine/Internal/Stream.hs new file mode 100644 index 00000000..e9676f50 --- /dev/null +++ b/src/Crypto/Saltine/Internal/Stream.hs @@ -0,0 +1,107 @@ +{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving, DeriveGeneric, ForeignFunctionInterface #-} +-- | +-- Module : Crypto.Saltine.Internal.Stream +-- Copyright : (c) Max Amanshauser 2021 +-- License : MIT +-- +-- Maintainer : max@lambdalifting.org +-- Stability : experimental +-- Portability : non-portable +-- +module Crypto.Saltine.Internal.Stream ( + stream_keybytes + , stream_noncebytes + , c_stream + , c_stream_xor + , Key(..) + , Nonce(..) +) where + +import Control.DeepSeq (NFData) +import Crypto.Saltine.Class +import Crypto.Saltine.Core.Hash (shorthash) +import Crypto.Saltine.Internal.Hash (nullShKey) +import Crypto.Saltine.Internal.Util as U +import Data.ByteString (ByteString) +import Data.Data (Data, Typeable) +import Data.Hashable (Hashable) +import Data.Monoid +import Foreign.C +import Foreign.Ptr +import GHC.Generics (Generic) + +import qualified Data.ByteString as S + +-- | An opaque 'stream' cryptographic key. +newtype Key = Key { unKey :: ByteString } deriving (Ord, Hashable, Data, Typeable, Generic, NFData) +instance Eq Key where + Key a == Key b = U.compare a b +instance Show Key where + show k = "Stream.Key {hashesTo = \"" <> (bin2hex . shorthash nullShKey $ encode k) <> "\"}" + +instance IsEncoding Key where + decode v = if S.length v == stream_keybytes + then Just (Key v) + else Nothing + {-# INLINE decode #-} + encode (Key v) = v + {-# INLINE encode #-} + +-- | An opaque 'stream' nonce. +newtype Nonce = Nonce { unNonce :: ByteString } deriving (Eq, Ord, Hashable, Data, Typeable, Generic, NFData) +instance Show Nonce where + show k = "Stream.Nonce " <> bin2hex (encode k) + +instance IsNonce Nonce where + zero = Nonce (S.replicate stream_noncebytes 0) + nudge (Nonce n) = Nonce (nudgeBS n) + +instance IsEncoding Nonce where + decode v = if S.length v == stream_noncebytes + then Just (Nonce v) + else Nothing + {-# INLINE decode #-} + encode (Nonce v) = v + {-# INLINE encode #-} + +stream_keybytes, stream_noncebytes :: Int + +-- Streams +-- | The size of a key for the cryptographic stream generation +stream_keybytes = fromIntegral c_crypto_stream_keybytes +-- | The size of a nonce for the cryptographic stream generation +stream_noncebytes = fromIntegral c_crypto_stream_noncebytes + +-- src/libsodium/crypto_stream/crypto_stream.c +-- src/libsodium/include/sodium/crypto_stream.h +foreign import ccall "crypto_stream_keybytes" + c_crypto_stream_keybytes :: CSize +foreign import ccall "crypto_stream_noncebytes" + c_crypto_stream_noncebytes :: CSize + + +foreign import ccall "crypto_stream" + c_stream :: Ptr CChar + -- ^ Stream output buffer + -> CULLong + -- ^ Length of stream to generate + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt + -- ^ Always 0 + +foreign import ccall "crypto_stream_xor" + c_stream_xor :: Ptr CChar + -- ^ Ciphertext output buffer + -> Ptr CChar + -- ^ Constant message buffer + -> CULLong + -- ^ Length of message buffer + -> Ptr CChar + -- ^ Constant nonce buffer + -> Ptr CChar + -- ^ Constant key buffer + -> IO CInt + -- ^ Always 0 diff --git a/src/Crypto/Saltine/Internal/Util.hs b/src/Crypto/Saltine/Internal/Util.hs new file mode 100644 index 00000000..5216d776 --- /dev/null +++ b/src/Crypto/Saltine/Internal/Util.hs @@ -0,0 +1,188 @@ +{-# LANGUAGE BangPatterns #-} + +module Crypto.Saltine.Internal.Util ( + module Crypto.Saltine.Internal.Util + , withCString + , allocaBytes +) +where + +import Data.ByteString (ByteString) +import Data.ByteString.Unsafe +import Data.Monoid +import Foreign.C +import Foreign.Marshal.Alloc (mallocBytes, allocaBytes) +import Foreign.Ptr +import GHC.Word (Word8) +import System.IO.Unsafe + +import qualified Data.ByteString as S +import qualified Data.ByteString.Char8 as S8 + +-- | Returns @Nothing@ if the subtraction would result in an +-- underflow or a negative number. +safeSubtract :: (Ord a, Num a) => a -> a -> Maybe a +x `safeSubtract` y = if y > x then Nothing else Just (x - y) + +-- | @snd . cycleSucc@ computes the 'succ' of a 'Bounded', 'Eq' 'Enum' +-- with wraparound. The @fst . cycleSuc@ is whether the wraparound +-- occurred (i.e. @fst . cycleSucc == (== maxBound)@). +cycleSucc :: (Bounded a, Enum a, Eq a) => a -> (Bool, a) +cycleSucc a = (top, if top then minBound else succ a) + where top = a == maxBound + +-- | Treats a 'ByteString' as a little endian bitstring and increments +-- it. +nudgeBS :: ByteString -> ByteString +nudgeBS i = fst $ S.unfoldrN (S.length i) go (True, i) where + go (toSucc, bs) = do + (hd, tl) <- S.uncons bs + let (top, hd') = cycleSucc hd + + if toSucc + then return (hd', (top, tl)) + else return (hd, (top && toSucc, tl)) + +-- | Computes the orbit of a endomorphism... in a very brute force +-- manner. Exists just for the below property. +-- +-- prop> length . orbit nudgeBS . S.pack . replicate 0 == (256^) +orbit :: Eq a => (a -> a) -> a -> [a] +orbit f a0 = orbit' (f a0) where + orbit' a = if a == a0 then [a0] else a : orbit' (f a) + +-- | 0-pad a 'ByteString' +pad :: Int -> ByteString -> ByteString +pad n = mappend (S.replicate n 0) + +-- | Remove a 0-padding from a 'ByteString' +unpad :: Int -> ByteString -> ByteString +unpad = S.drop + +-- | Converts a C-convention errno to an Either +handleErrno :: CInt -> (a -> Either String a) +handleErrno err a = case err of + 0 -> Right a + -1 -> Left "failed" + n -> Left ("unexpected error code: " ++ show n) + +unsafeDidSucceed :: IO CInt -> Bool +unsafeDidSucceed = go . unsafePerformIO + where go 0 = True + go _ = False + +withCStrings :: [String] -> ([CString] -> IO a) -> IO a +withCStrings = foldr (\v kk -> \k -> (withCString v) (\a -> kk (\as -> k (a:as)))) ($ []) + +withCStringLens :: [String] -> ([CStringLen] -> IO a) -> IO a +withCStringLens = foldr (\v kk -> \k -> (withCStringLen v) (\a -> kk (\as -> k (a:as)))) ($ []) + +-- | Convenience function for accessing constant C strings +constByteStrings :: [ByteString] -> ([CStringLen] -> IO b) -> IO b +constByteStrings = + foldr (\v kk -> \k -> (unsafeUseAsCStringLen v) (\a -> kk (\as -> k (a:as)))) ($ []) + +-- | Slightly safer cousin to 'buildUnsafeByteString' that remains in the +-- 'IO' monad. +buildUnsafeByteString' :: Int -> (Ptr CChar -> IO b) -> IO (b, ByteString) +buildUnsafeByteString' n k = do + ph <- mallocBytes n + bs <- unsafePackMallocCStringLen (ph, fromIntegral n) + out <- unsafeUseAsCString bs k + return (out, bs) + + +-- | Sometimes we have to deal with variable-length strings +buildUnsafeVariableByteString' :: Int -> (Ptr CChar -> IO b) -> IO (b, ByteString) +buildUnsafeVariableByteString' n k = do + ph <- mallocBytes n + out <- k ph + bs <- unsafePackMallocCString ph + return (out, bs) + +buildUnsafeVariableByteString :: Int -> (Ptr CChar -> IO b) -> (b, ByteString) +buildUnsafeVariableByteString n = unsafePerformIO . buildUnsafeVariableByteString' n + +-- | Extremely unsafe function, use with utmost care! Builds a new +-- ByteString using a ccall which is given access to the raw underlying +-- pointer. Overwrites are UNCHECKED and 'unsafePerformIO' is used so +-- it's difficult to predict the timing of the 'ByteString' creation. +buildUnsafeByteString :: Int -> (Ptr CChar -> IO b) -> (b, ByteString) +buildUnsafeByteString n = unsafePerformIO . buildUnsafeByteString' n + +-- | Build a sized random 'ByteString' using Sodium's bindings to +-- @/dev/urandom@. +randomByteString :: Int -> IO ByteString +randomByteString n = + snd <$> buildUnsafeByteString' n (`c_randombytes_buf` fromIntegral n) + +-- | To prevent a dependency on package 'errors' +hush :: Either s a -> Maybe a +hush = either (const Nothing) Just + +foreign import ccall "randombytes_buf" + c_randombytes_buf :: Ptr CChar -> CInt -> IO () + +-- | Constant time memory comparison +foreign import ccall unsafe "sodium_memcmp" + c_sodium_memcmp + :: Ptr CChar -- a + -> Ptr CChar -- b + -> CInt -- Length + -> IO CInt + +foreign import ccall unsafe "sodium_malloc" + c_sodium_malloc + :: CSize -> IO (Ptr a) + +foreign import ccall unsafe "sodium_free" + c_sodium_free + :: Ptr Word8 -> IO () + +-- | Not sure yet what to use this for +buildUnsafeScrubbedByteString' :: Int -> (Ptr CChar -> IO b) -> IO (b,ByteString) +buildUnsafeScrubbedByteString' n k = do + p <- c_sodium_malloc (fromIntegral n) + + bs <- unsafePackCStringFinalizer p n (c_sodium_free p) + out <- unsafeUseAsCString bs k + pure (out,bs) + +-- | Not sure yet what to use this for +buildUnsafeScrubbedByteString :: Int -> (Ptr CChar -> IO b) -> (b,ByteString) +buildUnsafeScrubbedByteString n = unsafePerformIO . buildUnsafeScrubbedByteString' n + +-- | Constant-time comparison +compare :: ByteString -> ByteString -> Bool +compare a b = + (S.length a == S.length b) && unsafePerformIO (constByteStrings [a, b] $ \ + [(bsa, _), (bsb,_)] -> + (== 0) <$> c_sodium_memcmp bsa bsb (fromIntegral $ S.length a)) + +-- | bin2hex conversion for showing various binary types +foreign import ccall unsafe "sodium_bin2hex" + c_sodium_bin2hex + :: Ptr CChar -- Target zone + -> CInt -- Max. length of target string (must be min. bin_len * 2 + 1) + -> Ptr CChar -- Source + -> CInt -- Source length + -> IO (Ptr CChar) + +bin2hex :: ByteString -> String +bin2hex bs = let tlen = S.length bs * 2 + 1 in + S8.unpack . S8.init . snd . buildUnsafeByteString tlen $ \t -> + constByteStrings [bs] $ \ + [(pbs, _)] -> + c_sodium_bin2hex t (fromIntegral tlen) pbs (fromIntegral $ S.length bs) + +uncurry3 :: (a -> b -> c -> d) -> ((a, b, c) -> d) +uncurry3 f ~(a,b,c) = f a b c + +uncurry5 :: (a -> b -> c -> d -> e -> f) -> ((a, b, c, d, e) -> f) +uncurry5 f ~(a,b,c,d,e) = f a b c d e + +(!&&!) :: Bool -> Bool -> Bool +(!&&!) !a !b = a && b + +(!||!) :: Bool -> Bool -> Bool +(!||!) !a !b = a || b diff --git a/tests/AEAD/AES256GCMProperties.hs b/tests/AEAD/AES256GCMProperties.hs new file mode 100644 index 00000000..1a9b4275 --- /dev/null +++ b/tests/AEAD/AES256GCMProperties.hs @@ -0,0 +1,125 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module AEAD.AES256GCMProperties ( + testAEADAES + ) where + +import Util +import Crypto.Saltine.Core.AEAD.AES256GCM +import Crypto.Saltine.Class (decode) +import Crypto.Saltine.Internal.AEAD.AES256GCM as Bytes + +import qualified Data.ByteString as S +import Data.Maybe (fromJust) +import Test.Framework.Providers.QuickCheck2 +import Test.Framework +import Test.QuickCheck (Property, (==>)) +import Test.QuickCheck.Arbitrary + +instance Arbitrary Nonce where + arbitrary = + do bs <- S.pack <$> vector Bytes.aead_aes256gcm_npubbytes + pure $ fromJust (decode bs) + +instance Arbitrary Key where + arbitrary = + do bs <- S.pack <$> vector Bytes.aead_aes256gcm_keybytes + pure $ fromJust (decode bs) + +-- | Ciphertext can be decrypted +rightInverseProp :: Key -> Nonce -> Message -> Message -> Bool +rightInverseProp k n (Message bs) (Message aad) = + Just bs == aeadOpen k n (aead k n bs aad) aad + +-- | Detached ciphertext/tag can be decrypted +rightInverseDetachedProp :: Key -> Nonce -> Message -> Message -> Bool +rightInverseDetachedProp k n (Message bs) (Message aad) = + let (tag,ct) = aeadDetached k n bs aad + in Just bs == aeadOpenDetached k n tag ct aad + +-- | Ciphertext cannot be decrypted if the ciphertext is perturbed +rightInverseFailureProp :: Key -> Nonce -> Message -> Message -> Perturb -> Property +rightInverseFailureProp k n (Message bs) (Message aad) p = + S.length bs /= 0 ==> + let ct = aead k n bs aad + fakeCT = perturb ct p + in fakeCT /= ct ==> Nothing == aeadOpen k n fakeCT aad + +-- | Ciphertext cannot be decrypted if the aad is perturbed +rightInverseAADFailureProp :: Key -> Nonce -> Message -> Message -> Message -> Property +rightInverseAADFailureProp k n (Message bs) (Message aad) (Message aad2) = + aad /= aad2 ==> Nothing == aeadOpen k n (aead k n bs aad) aad2 + +-- | Ciphertext cannot be decrypted if the tag is perturbed +rightInverseTagFailureProp :: Key -> Nonce -> Message -> Message -> Message -> Property +rightInverseTagFailureProp k n (Message bs) (Message aad) (Message newTag) = + let (tag,ct) = aeadDetached k n bs aad + in newTag /= tag ==> Nothing == aeadOpenDetached k n newTag ct aad + +-- | Ciphertext cannot be decrypted if the ciphertext is perturbed +rightInverseFailureDetachedProp :: Key -> Nonce -> Message -> Message -> Perturb -> Property +rightInverseFailureDetachedProp k n (Message bs) (Message aad) p@(Perturb pBytes) = + let (tag,ct) = aeadDetached k n bs aad + in S.length bs > length pBytes ==> + Nothing == aeadOpenDetached k n tag (perturb ct p) aad + +-- | Ciphertext cannot be decrypted with a different key +cannotDecryptKeyProp :: Key -> Key -> Nonce -> Message -> Message -> Property +cannotDecryptKeyProp k1 k2 n (Message bs) (Message aad) = + let ct = aead k1 n bs aad + in k1 /= k2 ==> Nothing == aeadOpen k2 n ct aad + +-- | Ciphertext cannot be decrypted with a different key +cannotDecryptKeyDetachedProp :: Key -> Key -> Nonce -> Message -> Message -> Property +cannotDecryptKeyDetachedProp k1 k2 n (Message bs) (Message aad) = + let (tag,ct) = aeadDetached k1 n bs aad + in k1 /= k2 ==> Nothing == aeadOpenDetached k2 n tag ct aad + +-- | Ciphertext cannot be decrypted with a different nonce +cannotDecryptNonceProp :: Key -> Nonce -> Nonce -> Message -> Message -> Property +cannotDecryptNonceProp k n1 n2 (Message bs) (Message aad) = + n1 /= n2 ==> Nothing == aeadOpen k n2 (aead k n1 bs aad) aad + +-- | Ciphertext cannot be decrypted with a different nonce +cannotDecryptNonceDetachedProp :: Key -> Nonce -> Nonce -> Message -> Message -> Property +cannotDecryptNonceDetachedProp k n1 n2 (Message bs) (Message aad) = + let (tag,ct) = aeadDetached k n1 bs aad + in n1 /= n2 ==> Nothing == aeadOpenDetached k n2 tag ct aad + +testAEADAES :: Test +testAEADAES = buildTest $ + + return $ testGroup "...Internal.AEAD.AES256GCM" $ if not aead_aes256gcm_available then [] else [ + testProperty "Can decrypt ciphertext" rightInverseProp, + + testProperty "Can decrypt ciphertext (detached)" rightInverseDetachedProp, + + testGroup "Cannot decrypt ciphertext when..." [ + + testProperty "... ciphertext is perturbed" + $ rightInverseFailureProp, + + testProperty "... AAD is perturbed" + $ rightInverseAADFailureProp, + + testProperty "... ciphertext is perturbed (detached)" + $ rightInverseFailureDetachedProp, + + testProperty "... tag is perturbed (detached)" + $ rightInverseTagFailureProp, + + testProperty "... using the wrong key" + $ cannotDecryptKeyProp, + + testProperty "... using the wrong key (detached)" + $ cannotDecryptKeyDetachedProp, + + testProperty "... using the wrong nonce" + $ cannotDecryptNonceProp, + + testProperty "... using the wrong nonce (detached" + $ cannotDecryptNonceDetachedProp + + ] + ] diff --git a/tests/AEAD/ChaCha20Poly1305IETFProperties.hs b/tests/AEAD/ChaCha20Poly1305IETFProperties.hs new file mode 100644 index 00000000..a9416cb1 --- /dev/null +++ b/tests/AEAD/ChaCha20Poly1305IETFProperties.hs @@ -0,0 +1,128 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module AEAD.ChaCha20Poly1305IETFProperties ( + testAEADIETF + ) where + +import Util +import Crypto.Saltine.Core.AEAD.ChaCha20Poly1305IETF +import Crypto.Saltine.Class (decode) +import Crypto.Saltine.Internal.AEAD.ChaCha20Poly1305IETF as Bytes + +import qualified Data.ByteString as S +import Data.Maybe (fromJust) +import Test.Framework.Providers.QuickCheck2 +import Test.Framework +import Test.QuickCheck (Property, (==>)) +import Test.QuickCheck.Arbitrary + +instance Arbitrary Nonce where + arbitrary = + do bs <- S.pack <$> vector Bytes.aead_chacha20poly1305_ietf_npubbytes + pure $ fromJust (decode bs) + +instance Arbitrary Key where + arbitrary = + do bs <- S.pack <$> vector Bytes.aead_chacha20poly1305_ietf_keybytes + pure $ fromJust (decode bs) + +-- | Ciphertext can be decrypted +rightInverseProp :: Key -> Nonce -> Message -> Message -> Bool +rightInverseProp k n (Message bs) (Message aad) = + Just bs == aeadOpen k n (aead k n bs aad) aad + +-- | Detached ciphertext/tag can be decrypted +rightInverseDetachedProp :: Key -> Nonce -> Message -> Message -> Bool +rightInverseDetachedProp k n (Message bs) (Message aad) = + let (tag,ct) = aeadDetached k n bs aad + in Just bs == aeadOpenDetached k n tag ct aad + +-- | Ciphertext cannot be decrypted if the ciphertext is perturbed +rightInverseFailureProp :: Key -> Nonce -> Message -> Message -> Perturb -> Property +rightInverseFailureProp k n (Message bs) (Message aad) p = + S.length bs /= 0 ==> + let ct = aead k n bs aad + fakeCT = perturb ct p + in fakeCT /= ct ==> Nothing == aeadOpen k n fakeCT aad + +-- | Ciphertext cannot be decrypted if the aad is perturbed +rightInverseAADFailureProp :: Key -> Nonce -> Message -> Message -> Message -> Property +rightInverseAADFailureProp k n (Message bs) (Message aad) (Message aad2) = + aad /= aad2 ==> Nothing == aeadOpen k n (aead k n bs aad) aad2 + +-- | Ciphertext cannot be decrypted if the tag is perturbed +rightInverseTagFailureProp :: Key -> Nonce -> Message -> Message -> Message -> Property +rightInverseTagFailureProp k n (Message bs) (Message aad) (Message newTag) = + let (tag,ct) = aeadDetached k n bs aad + in newTag /= tag ==> Nothing == aeadOpenDetached k n newTag ct aad + +-- | Ciphertext cannot be decrypted if the ciphertext is perturbed +rightInverseFailureDetachedProp :: Key -> Nonce -> Message -> Message -> Perturb -> Property +rightInverseFailureDetachedProp k n (Message bs) (Message aad) p@(Perturb pBytes) = + let (tag,ct) = aeadDetached k n bs aad + in S.length bs > length pBytes ==> + Nothing == aeadOpenDetached k n tag (perturb ct p) aad + +-- | Ciphertext cannot be decrypted with a different key +cannotDecryptKeyProp :: Key -> Key -> Nonce -> Message -> Message -> Property +cannotDecryptKeyProp k1 k2 n (Message bs) (Message aad) = + let ct = aead k1 n bs aad + in k1 /= k2 ==> Nothing == aeadOpen k2 n ct aad + +-- | Ciphertext cannot be decrypted with a different key +cannotDecryptKeyDetachedProp :: Key -> Key -> Nonce -> Message -> Message -> Property +cannotDecryptKeyDetachedProp k1 k2 n (Message bs) (Message aad) = + let (tag,ct) = aeadDetached k1 n bs aad + in k1 /= k2 ==> Nothing == aeadOpenDetached k2 n tag ct aad + +-- | Ciphertext cannot be decrypted with a different nonce +cannotDecryptNonceProp :: Key -> Nonce -> Nonce -> Message -> Message -> Property +cannotDecryptNonceProp k n1 n2 (Message bs) (Message aad) = + n1 /= n2 ==> Nothing == aeadOpen k n2 (aead k n1 bs aad) aad + +-- | Ciphertext cannot be decrypted with a different nonce +cannotDecryptNonceDetachedProp :: Key -> Nonce -> Nonce -> Message -> Message -> Property +cannotDecryptNonceDetachedProp k n1 n2 (Message bs) (Message aad) = + let (tag,ct) = aeadDetached k n1 bs aad + in n1 /= n2 ==> Nothing == aeadOpenDetached k n2 tag ct aad + +testAEADIETF :: Test +testAEADIETF = buildTest $ do + + return $ testGroup "...Internal.AEAD.ChaCha20Poly1305IETF" [ + + testProperty "Can decrypt ciphertext" + $ rightInverseProp, + + testProperty "Can decrypt ciphertext (detached)" + $ rightInverseDetachedProp, + + testGroup "Cannot decrypt ciphertext when..." [ + + testProperty "... ciphertext is perturbed" + $ rightInverseFailureProp, + + testProperty "... AAD is perturbed" + $ rightInverseAADFailureProp, + + testProperty "... ciphertext is perturbed (detached)" + $ rightInverseFailureDetachedProp, + + testProperty "... tag is perturbed (detached)" + $ rightInverseTagFailureProp, + + testProperty "... using the wrong key" + $ cannotDecryptKeyProp, + + testProperty "... using the wrong key (detached)" + $ cannotDecryptKeyDetachedProp, + + testProperty "... using the wrong nonce" + $ cannotDecryptNonceProp, + + testProperty "... using the wrong nonce (detached" + $ cannotDecryptNonceDetachedProp + + ] + ] diff --git a/tests/AEAD/ChaCha20Poly1305Properties.hs b/tests/AEAD/ChaCha20Poly1305Properties.hs new file mode 100644 index 00000000..3f6b3c9d --- /dev/null +++ b/tests/AEAD/ChaCha20Poly1305Properties.hs @@ -0,0 +1,128 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module AEAD.ChaCha20Poly1305Properties ( + testAEADChaCha20 + ) where + +import Util +import Crypto.Saltine.Core.AEAD.ChaCha20Poly1305IETF +import Crypto.Saltine.Class (decode) +import Crypto.Saltine.Internal.AEAD.ChaCha20Poly1305IETF as Bytes + +import qualified Data.ByteString as S +import Data.Maybe (fromJust) +import Test.Framework.Providers.QuickCheck2 +import Test.Framework +import Test.QuickCheck (Property, (==>)) +import Test.QuickCheck.Arbitrary + +instance Arbitrary Nonce where + arbitrary = + do bs <- S.pack <$> vector Bytes.aead_chacha20poly1305_ietf_npubbytes + pure $ fromJust (decode bs) + +instance Arbitrary Key where + arbitrary = + do bs <- S.pack <$> vector Bytes.aead_chacha20poly1305_ietf_keybytes + pure $ fromJust (decode bs) + +-- | Ciphertext can be decrypted +rightInverseProp :: Key -> Nonce -> Message -> Message -> Bool +rightInverseProp k n (Message bs) (Message aad) = + Just bs == aeadOpen k n (aead k n bs aad) aad + +-- | Detached ciphertext/tag can be decrypted +rightInverseDetachedProp :: Key -> Nonce -> Message -> Message -> Bool +rightInverseDetachedProp k n (Message bs) (Message aad) = + let (tag,ct) = aeadDetached k n bs aad + in Just bs == aeadOpenDetached k n tag ct aad + +-- | Ciphertext cannot be decrypted if the ciphertext is perturbed +rightInverseFailureProp :: Key -> Nonce -> Message -> Message -> Perturb -> Property +rightInverseFailureProp k n (Message bs) (Message aad) p = + S.length bs /= 0 ==> + let ct = aead k n bs aad + fakeCT = perturb ct p + in fakeCT /= ct ==> Nothing == aeadOpen k n fakeCT aad + +-- | Ciphertext cannot be decrypted if the aad is perturbed +rightInverseAADFailureProp :: Key -> Nonce -> Message -> Message -> Message -> Property +rightInverseAADFailureProp k n (Message bs) (Message aad) (Message aad2) = + aad /= aad2 ==> Nothing == aeadOpen k n (aead k n bs aad) aad2 + +-- | Ciphertext cannot be decrypted if the tag is perturbed +rightInverseTagFailureProp :: Key -> Nonce -> Message -> Message -> Message -> Property +rightInverseTagFailureProp k n (Message bs) (Message aad) (Message newTag) = + let (tag,ct) = aeadDetached k n bs aad + in newTag /= tag ==> Nothing == aeadOpenDetached k n newTag ct aad + +-- | Ciphertext cannot be decrypted if the ciphertext is perturbed +rightInverseFailureDetachedProp :: Key -> Nonce -> Message -> Message -> Perturb -> Property +rightInverseFailureDetachedProp k n (Message bs) (Message aad) p@(Perturb pBytes) = + let (tag,ct) = aeadDetached k n bs aad + in S.length bs > length pBytes ==> + Nothing == aeadOpenDetached k n tag (perturb ct p) aad + +-- | Ciphertext cannot be decrypted with a different key +cannotDecryptKeyProp :: Key -> Key -> Nonce -> Message -> Message -> Property +cannotDecryptKeyProp k1 k2 n (Message bs) (Message aad) = + let ct = aead k1 n bs aad + in k1 /= k2 ==> Nothing == aeadOpen k2 n ct aad + +-- | Ciphertext cannot be decrypted with a different key +cannotDecryptKeyDetachedProp :: Key -> Key -> Nonce -> Message -> Message -> Property +cannotDecryptKeyDetachedProp k1 k2 n (Message bs) (Message aad) = + let (tag,ct) = aeadDetached k1 n bs aad + in k1 /= k2 ==> Nothing == aeadOpenDetached k2 n tag ct aad + +-- | Ciphertext cannot be decrypted with a different nonce +cannotDecryptNonceProp :: Key -> Nonce -> Nonce -> Message -> Message -> Property +cannotDecryptNonceProp k n1 n2 (Message bs) (Message aad) = + n1 /= n2 ==> Nothing == aeadOpen k n2 (aead k n1 bs aad) aad + +-- | Ciphertext cannot be decrypted with a different nonce +cannotDecryptNonceDetachedProp :: Key -> Nonce -> Nonce -> Message -> Message -> Property +cannotDecryptNonceDetachedProp k n1 n2 (Message bs) (Message aad) = + let (tag,ct) = aeadDetached k n1 bs aad + in n1 /= n2 ==> Nothing == aeadOpenDetached k n2 tag ct aad + +testAEADChaCha20 :: Test +testAEADChaCha20 = buildTest $ do + + return $ testGroup "...Internal.AEAD.ChaCha20Poly1305" [ + + testProperty "Can decrypt ciphertext" + $ rightInverseProp, + + testProperty "Can decrypt ciphertext (detached)" + $ rightInverseDetachedProp, + + testGroup "Cannot decrypt ciphertext when..." [ + + testProperty "... ciphertext is perturbed" + $ rightInverseFailureProp, + + testProperty "... AAD is perturbed" + $ rightInverseAADFailureProp, + + testProperty "... ciphertext is perturbed (detached)" + $ rightInverseFailureDetachedProp, + + testProperty "... tag is perturbed (detached)" + $ rightInverseTagFailureProp, + + testProperty "... using the wrong key" + $ cannotDecryptKeyProp, + + testProperty "... using the wrong key (detached)" + $ cannotDecryptKeyDetachedProp, + + testProperty "... using the wrong nonce" + $ cannotDecryptNonceProp, + + testProperty "... using the wrong nonce (detached" + $ cannotDecryptNonceDetachedProp + + ] + ] diff --git a/tests/AEAD/XChaCha20Poly1305Properties.hs b/tests/AEAD/XChaCha20Poly1305Properties.hs new file mode 100644 index 00000000..4ae79149 --- /dev/null +++ b/tests/AEAD/XChaCha20Poly1305Properties.hs @@ -0,0 +1,128 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module AEAD.XChaCha20Poly1305Properties ( + testAEADXChaCha20 + ) where + +import Util +import Crypto.Saltine.Core.AEAD.XChaCha20Poly1305 +import Crypto.Saltine.Class (decode) +import Crypto.Saltine.Internal.AEAD.XChaCha20Poly1305 as Bytes + +import qualified Data.ByteString as S +import Data.Maybe (fromJust) +import Test.Framework.Providers.QuickCheck2 +import Test.Framework +import Test.QuickCheck (Property, (==>)) +import Test.QuickCheck.Arbitrary + +instance Arbitrary Nonce where + arbitrary = + do bs <- S.pack <$> vector Bytes.aead_xchacha20poly1305_ietf_npubbytes + pure $ fromJust (decode bs) + +instance Arbitrary Key where + arbitrary = + do bs <- S.pack <$> vector Bytes.aead_xchacha20poly1305_ietf_keybytes + pure $ fromJust (decode bs) + +-- | Ciphertext can be decrypted +rightInverseProp :: Key -> Nonce -> Message -> Message -> Bool +rightInverseProp k n (Message bs) (Message aad) = + Just bs == aeadOpen k n (aead k n bs aad) aad + +-- | Detached ciphertext/tag can be decrypted +rightInverseDetachedProp :: Key -> Nonce -> Message -> Message -> Bool +rightInverseDetachedProp k n (Message bs) (Message aad) = + let (tag,ct) = aeadDetached k n bs aad + in Just bs == aeadOpenDetached k n tag ct aad + +-- | Ciphertext cannot be decrypted if the ciphertext is perturbed +rightInverseFailureProp :: Key -> Nonce -> Message -> Message -> Perturb -> Property +rightInverseFailureProp k n (Message bs) (Message aad) p = + S.length bs /= 0 ==> + let ct = aead k n bs aad + fakeCT = perturb ct p + in fakeCT /= ct ==> Nothing == aeadOpen k n fakeCT aad + +-- | Ciphertext cannot be decrypted if the aad is perturbed +rightInverseAADFailureProp :: Key -> Nonce -> Message -> Message -> Message -> Property +rightInverseAADFailureProp k n (Message bs) (Message aad) (Message aad2) = + aad /= aad2 ==> Nothing == aeadOpen k n (aead k n bs aad) aad2 + +-- | Ciphertext cannot be decrypted if the tag is perturbed +rightInverseTagFailureProp :: Key -> Nonce -> Message -> Message -> Message -> Property +rightInverseTagFailureProp k n (Message bs) (Message aad) (Message newTag) = + let (tag,ct) = aeadDetached k n bs aad + in newTag /= tag ==> Nothing == aeadOpenDetached k n newTag ct aad + +-- | Ciphertext cannot be decrypted if the ciphertext is perturbed +rightInverseFailureDetachedProp :: Key -> Nonce -> Message -> Message -> Perturb -> Property +rightInverseFailureDetachedProp k n (Message bs) (Message aad) p@(Perturb pBytes) = + let (tag,ct) = aeadDetached k n bs aad + in S.length bs > length pBytes ==> + Nothing == aeadOpenDetached k n tag (perturb ct p) aad + +-- | Ciphertext cannot be decrypted with a different key +cannotDecryptKeyProp :: Key -> Key -> Nonce -> Message -> Message -> Property +cannotDecryptKeyProp k1 k2 n (Message bs) (Message aad) = + let ct = aead k1 n bs aad + in k1 /= k2 ==> Nothing == aeadOpen k2 n ct aad + +-- | Ciphertext cannot be decrypted with a different key +cannotDecryptKeyDetachedProp :: Key -> Key -> Nonce -> Message -> Message -> Property +cannotDecryptKeyDetachedProp k1 k2 n (Message bs) (Message aad) = + let (tag,ct) = aeadDetached k1 n bs aad + in k1 /= k2 ==> Nothing == aeadOpenDetached k2 n tag ct aad + +-- | Ciphertext cannot be decrypted with a different nonce +cannotDecryptNonceProp :: Key -> Nonce -> Nonce -> Message -> Message -> Property +cannotDecryptNonceProp k n1 n2 (Message bs) (Message aad) = + n1 /= n2 ==> Nothing == aeadOpen k n2 (aead k n1 bs aad) aad + +-- | Ciphertext cannot be decrypted with a different nonce +cannotDecryptNonceDetachedProp :: Key -> Nonce -> Nonce -> Message -> Message -> Property +cannotDecryptNonceDetachedProp k n1 n2 (Message bs) (Message aad) = + let (tag,ct) = aeadDetached k n1 bs aad + in n1 /= n2 ==> Nothing == aeadOpenDetached k n2 tag ct aad + +testAEADXChaCha20 :: Test +testAEADXChaCha20 = buildTest $ do + + return $ testGroup "...Internal.AEAD.XChaCha20Poly1305" [ + + testProperty "Can decrypt ciphertext" + $ rightInverseProp, + + testProperty "Can decrypt ciphertext (detached)" + $ rightInverseDetachedProp, + + testGroup "Cannot decrypt ciphertext when..." [ + + testProperty "... ciphertext is perturbed" + $ rightInverseFailureProp, + + testProperty "... AAD is perturbed" + $ rightInverseAADFailureProp, + + testProperty "... ciphertext is perturbed (detached)" + $ rightInverseFailureDetachedProp, + + testProperty "... tag is perturbed (detached)" + $ rightInverseTagFailureProp, + + testProperty "... using the wrong key" + $ cannotDecryptKeyProp, + + testProperty "... using the wrong key (detached)" + $ cannotDecryptKeyDetachedProp, + + testProperty "... using the wrong nonce" + $ cannotDecryptNonceProp, + + testProperty "... using the wrong nonce (detached" + $ cannotDecryptNonceDetachedProp + + ] + ] diff --git a/tests/AuthProperties.hs b/tests/AuthProperties.hs new file mode 100644 index 00000000..ffb9afa7 --- /dev/null +++ b/tests/AuthProperties.hs @@ -0,0 +1,21 @@ +{-# LANGUAGE OverloadedStrings #-} + +module AuthProperties ( + testAuth + ) where + +import Util +import Crypto.Saltine.Core.Auth + +import Test.Framework.Providers.QuickCheck2 +import Test.Framework + +testAuth :: Test +testAuth = buildTest $ do + k <- newKey + return $ testGroup "...Internal.Auth" [ + + testProperty "Authenticates message" + $ \(Message bs) -> verify k (auth k bs) bs == True + + ] diff --git a/tests/BoxProperties.hs b/tests/BoxProperties.hs new file mode 100644 index 00000000..e53780f4 --- /dev/null +++ b/tests/BoxProperties.hs @@ -0,0 +1,110 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE OverloadedLists #-} + +module BoxProperties ( + testBox + ) where + +import Crypto.Saltine.Core.Box +import Data.Monoid +import Test.Framework.Providers.QuickCheck2 +import Test.Framework +import Test.QuickCheck.Property +import Test.QuickCheck.Monadic +import Util + +-- | Ciphertext can be decrypted +rightInverseProp :: Keypair -> Keypair -> Nonce -> Message -> Bool +rightInverseProp (Keypair sk1 pk1) (Keypair sk2 pk2) n (Message bs) = + Just bs == boxOpen pk1 sk2 n (box pk2 sk1 n bs) + +-- | Cannot decrypt without the corrent secret key +rightInverseFailureProp1 :: Keypair -> Keypair -> Nonce -> Message -> Perturb -> Bool +rightInverseFailureProp1 (Keypair sk1 pk1) (Keypair sk2 pk2) n (Message bs) p = + Nothing == boxOpen pk1 (perturb sk2 ([0] <> p)) n (box pk2 sk1 n bs) + +-- | Cannot decrypt when not sent to you +rightInverseFailureProp2 :: Keypair -> Keypair -> Nonce -> Message -> Perturb -> Bool +rightInverseFailureProp2 (Keypair sk1 pk1) (Keypair sk2 pk2) n (Message bs) p = + Nothing == boxOpen pk1 sk2 n (box (perturb pk2 p) sk1 n bs) + +-- | Ciphertext cannot be decrypted (verification failure) if the +-- ciphertext is perturbed +rightInverseFailureProp3 :: Keypair -> Keypair -> Nonce -> Message -> Perturb -> Bool +rightInverseFailureProp3 (Keypair sk1 pk1) (Keypair sk2 pk2) n (Message bs) p = + Nothing == boxOpen pk1 sk2 n (perturb (box pk2 sk1 n bs) p) + +-- | Ciphertext cannot be decrypted with a different nonce +cannotDecryptNonceProp + :: Keypair -> Keypair -> Nonce -> Nonce -> Message -> Bool +cannotDecryptNonceProp (Keypair sk1 pk1) (Keypair sk2 pk2) n1 n2 (Message bs) = + Nothing == boxOpen pk1 sk2 n2 (box pk2 sk1 n1 bs) + +-- | BeforeNM creates identical secret keys when called in an +-- anti-symmetric fashion. +beforeNMCreateSecretKeyProp :: Test.QuickCheck.Property.Property +beforeNMCreateSecretKeyProp = monadicIO . (assert =<<) . run $ do + Keypair sk1 pk1 <- newKeypair + Keypair sk2 pk2 <- newKeypair + let ck_1for2 = beforeNM sk1 pk2 + ck_2for1 = beforeNM sk2 pk1 + return (ck_1for2 == ck_2for1) + +-- | Ciphertext can be decrypted using combined keys +rightInverseAfterNMProp + :: CombinedKey -> CombinedKey -> Nonce -> Message -> Bool +rightInverseAfterNMProp ck_1for2 ck_2for1 n (Message bs) = + Just bs == boxOpenAfterNM ck_2for1 n (boxAfterNM ck_1for2 n bs) + +-- | Perturbed ciphertext cannot be decrypted using combined keys +rightInverseFailureAfterNMProp1 + :: CombinedKey -> CombinedKey -> Nonce -> Message -> Perturb -> Bool +rightInverseFailureAfterNMProp1 ck_1for2 ck_2for1 n (Message bs) p = + Nothing == boxOpenAfterNM ck_2for1 n (perturb (boxAfterNM ck_1for2 n bs) p) + +testBox :: Test +testBox = buildTest $ do + kp1@(Keypair sk1 pk1) <- newKeypair + kp2@(Keypair sk2 pk2) <- newKeypair + let ck_1for2 = beforeNM sk1 pk2 + ck_2for1 = beforeNM sk2 pk1 + n1 <- newNonce + n2 <- newNonce + + return $ testGroup "...Internal.Box" [ + + testGroup "Can decrypt ciphertext using..." [ + + testProperty "... public key/secret key" + $ rightInverseProp kp1 kp2 n1 , + + testProperty "... combined key" + $ rightInverseAfterNMProp ck_1for2 ck_2for1 n1 + + ], + + testGroup "Fail to verify ciphertext when..." [ + + testProperty "... not using proper secret key" + $ rightInverseFailureProp1 kp1 kp2 n1, + + testProperty "... not actually sent to you" + $ rightInverseFailureProp2 kp1 kp2 n1, + + testProperty "... ciphertext has been perturbed" + $ rightInverseFailureProp3 kp1 kp2 n1, + + testProperty "... using the wrong nonce" + $ cannotDecryptNonceProp kp1 kp2 n1 n2, + + testProperty "... using the wrong combined key" + $ rightInverseFailureAfterNMProp1 ck_1for2 ck_2for1 n1 + + ], + + testGroup "(properties)" [ + + testProperty "beforeNM is anti-symmetric" beforeNMCreateSecretKeyProp + + ] + ] diff --git a/tests/HashProperties.hs b/tests/HashProperties.hs new file mode 100644 index 00000000..ef67bf36 --- /dev/null +++ b/tests/HashProperties.hs @@ -0,0 +1,46 @@ +{-# LANGUAGE OverloadedStrings #-} + +module HashProperties ( + testHash + ) where + +import Util +import Crypto.Saltine.Core.Hash + +import qualified Data.ByteString as S +import Test.Framework.Providers.QuickCheck2 +import Test.Framework +import Test.QuickCheck + +testHash :: Test +testHash = buildTest $ do + shKey <- newShorthashKey + shKey2 <- newShorthashKey + ghKey <- newGenerichashKey 24 >>= maybe undefined return + ghKey2 <- newGenerichashKey 24 >>= maybe undefined return + let ghOutLen = maybe undefined id $ generichashOutLen 32 + + return $ testGroup "...Internal.Hash" [ + + testProperty "No two hashes are alike" + $ \(Message bs1, Message bs2) -> bs1 /= bs2 ==> hash bs1 /= hash bs2, + + testProperty "Hash of empty ByteString is correct" + $ \(Message bs) -> (bs == S.empty) ==> hash bs == (read hashEmptyBS :: S.ByteString), + + testProperty "No two shorthashes are alike" + $ \(Message bs1, Message bs2) -> bs1 /= bs2 ==> shorthash shKey bs1 /= shorthash shKey bs2, + + testProperty "Different keys produce different shorthashes" + $ \(Message bs) -> shorthash shKey bs /= shorthash shKey2 bs, + + testProperty "No two generic hashes are alike" + $ \(Message bs1, Message bs2) -> bs1 /= bs2 ==> generichash ghKey bs1 ghOutLen /= generichash ghKey bs2 ghOutLen, + + testProperty "Different keys produce different generichashes" + $ \(Message bs) -> generichash ghKey bs ghOutLen /= generichash ghKey2 bs ghOutLen + + ] + + where + hashEmptyBS = "\"\207\131\225\&5~\239\184\189\241T(P\214m\128\a\214 \228\ENQ\vW\NAK\220\131\244\169!\211l\233\206G\208\209<]\133\242\176\255\131\CAN\210\135~\236/c\185\&1\189GAz\129\165\&82z\249'\218>\"" diff --git a/tests/Main.hs b/tests/Main.hs new file mode 100644 index 00000000..88e31c9d --- /dev/null +++ b/tests/Main.hs @@ -0,0 +1,51 @@ +{-# LANGUAGE OverloadedStrings #-} + +module Main where + +import SecretBoxProperties (testSecretBox) +import AEAD.ChaCha20Poly1305Properties (testAEADChaCha20) +import AEAD.ChaCha20Poly1305IETFProperties (testAEADIETF) +import AEAD.XChaCha20Poly1305Properties (testAEADXChaCha20) +import AEAD.AES256GCMProperties (testAEADAES) +import BoxProperties (testBox) +import SealedBoxProperties (testSealedBox) +import StreamProperties (testStream) +import AuthProperties (testAuth) +import OneTimeAuthProperties (testOneTimeAuth) +import SignProperties (testSign) +import HashProperties (testHash) +import ScalarMultProperties (testScalarMult) +import PasswordProperties (testPassword) +import UtilProperties (testUtils) +import Crypto.Saltine + +import Test.Framework + +runOpts :: RunnerOptions +runOpts = mempty { ropt_color_mode = Just ColorAlways + , ropt_test_options = Just testOpts + } + +testOpts :: TestOptions +testOpts = mempty { topt_maximum_generated_tests = Just 20000 } + +main :: IO () +main = do + sodiumInit + flip defaultMainWithOpts runOpts [ + testUtils, + testBox, + testSealedBox, + testSecretBox, + testAEADChaCha20, + testAEADIETF, + testAEADXChaCha20, + testAEADAES, + testStream, + testAuth, + testOneTimeAuth, + testSign, + testHash, + testScalarMult, + testPassword + ] diff --git a/tests/OneTimeAuthProperties.hs b/tests/OneTimeAuthProperties.hs new file mode 100644 index 00000000..d0c75c69 --- /dev/null +++ b/tests/OneTimeAuthProperties.hs @@ -0,0 +1,22 @@ +{-# LANGUAGE OverloadedStrings #-} + +module OneTimeAuthProperties ( + testOneTimeAuth + ) where + +import Util + +import Crypto.Saltine.Core.OneTimeAuth + +import Test.Framework.Providers.QuickCheck2 +import Test.Framework + +testOneTimeAuth :: Test +testOneTimeAuth = buildTest $ do + k <- newKey + return $ testGroup "...Internal.Auth (one-time)" [ + + testProperty "Authenticates message" + $ \(Message bs) -> verify k (auth k bs) bs == True + + ] diff --git a/tests/PasswordProperties.hs b/tests/PasswordProperties.hs new file mode 100644 index 00000000..8d1f1892 --- /dev/null +++ b/tests/PasswordProperties.hs @@ -0,0 +1,97 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE OverloadedLists #-} + +module PasswordProperties ( + testPassword +) where + +import Crypto.Saltine.Core.Password +import Crypto.Saltine.Internal.Util +import Data.Maybe (isJust, isNothing, fromJust) +import Data.Monoid +import Data.Text (Text) +import Test.Framework.Providers.QuickCheck2 +import Test.Framework +import Test.QuickCheck + +import qualified Crypto.Saltine.Internal.Password as I +import qualified Data.Text as T + +instance Arbitrary Text where + arbitrary = T.pack <$> arbitrary + +instance Arbitrary Algorithm where + arbitrary = elements $ enumFromTo minBound maxBound + +-- | Sadly using the actual maximum limit is just way too slow +instance Arbitrary Memlimit where + arbitrary = I.Memlimit <$> chooseInt ( max I.pwhash_argon2i_memlimit_min I.pwhash_argon2id_memlimit_min + , max I.pwhash_argon2i_memlimit_min I.pwhash_argon2id_memlimit_min * 4 + ) + +instance Arbitrary Opslimit where + arbitrary = I.Opslimit <$> chooseInt ( max I.pwhash_argon2i_opslimit_min I.pwhash_argon2id_opslimit_min + , max I.pwhash_argon2i_opslimit_min I.pwhash_argon2id_opslimit_min * 4 + ) + +instance Arbitrary Policy where + arbitrary = applyArbitrary3 Policy + +rightInverseProp :: Text -> Policy -> IO Bool +rightInverseProp pw pol = do + h <- pwhashStr pw pol + pure $ pwhashStrVerify (fromJust h) pw + +rightInverseFailureProp1 :: Text -> Policy -> Text -> IO Bool +rightInverseFailureProp1 pw pol per = + let npw = T.reverse pw <> T.pack "0" <> per + in do + h <- pwhashStr pw pol + pure . not $ pwhashStrVerify (fromJust h) npw + +rightProp :: Text -> Policy -> IO Bool +rightProp pw pol = do + h <- pwhashStr pw pol + pure $ Just False == needsRehash (opsPolicy pol) (memPolicy pol) (fromJust h) + +rightFailureProp :: Text -> Opslimit -> Opslimit -> Memlimit -> Memlimit -> IO Bool +rightFailureProp pw ops1 ops2 mem1 mem2 = do + h <- pwhashStr pw (Policy ops1 mem1 defaultAlgorithm) + pure $ Just True == needsRehash ops2 mem2 (fromJust h) + || ops1 == ops2 + +rightFailureProp2 :: Text -> Opslimit -> Memlimit -> Bool +rightFailureProp2 invhash ops mem = + isNothing $ needsRehash ops mem (I.PasswordHash invhash) + +rightProp2 :: Salt -> Text -> Policy -> Gen Bool +rightProp2 salt pw pol = do + i <- chooseInt (I.pwhash_bytes_min, 1024) + + pure $ isJust $ pwhash pw i salt pol + + +testPassword :: Test +testPassword = buildTest $ do + salt <- newSalt + + return $ testGroup "... Password" [ + + testProperty "Can hash passwords and verify them..." + $ ioProperty . uncurry rightInverseProp, + + testProperty "Hashed passwords cannot be verified with the wrong password..." + $ ioProperty . uncurry3 rightInverseFailureProp1, + + testProperty "Hashed passwords do not need to be rehashed with the same policy..." + $ ioProperty . uncurry rightProp, + + testProperty "Hashed passwords do need to be rehashed with a different policy..." + $ ioProperty . uncurry5 rightFailureProp, + + testProperty "needsRehash detects invalid hashes..." + rightFailureProp2, + + testProperty "Deriving a key from a password..." + (rightProp2 salt) + ] diff --git a/tests/ScalarMultProperties.hs b/tests/ScalarMultProperties.hs new file mode 100644 index 00000000..dd697078 --- /dev/null +++ b/tests/ScalarMultProperties.hs @@ -0,0 +1,74 @@ +{-# LANGUAGE OverloadedStrings #-} + +module ScalarMultProperties ( + testScalarMult + ) where + +import Util +import Crypto.Saltine.Class +import Crypto.Saltine.Core.ScalarMult + +import qualified Data.ByteString as S +import Data.Maybe (fromJust) +import Test.Framework.Providers.QuickCheck2 +import Test.Framework + +-- Test vectors extracted from "Cryptography in NaCl", +-- http://cr.yp.to/highspeed/naclcrypto-20090310.pdf +alicesk, bobsk :: Scalar +alicesk = fromJust . decode $ S.pack + [0x77,0x07,0x6d,0x0a,0x73,0x18,0xa5,0x7d + ,0x3c,0x16,0xc1,0x72,0x51,0xb2,0x66,0x45 + ,0xdf,0x4c,0x2f,0x87,0xeb,0xc0,0x99,0x2a + ,0xb1,0x77,0xfb,0xa5,0x1d,0xb9,0x2c,0x2a] +bobsk = fromJust . decode $ S.pack + [0x5d,0xab,0x08,0x7e,0x62,0x4a,0x8a,0x4b + ,0x79,0xe1,0x7f,0x8b,0x83,0x80,0x0e,0xe6 + ,0x6f,0x3b,0xb1,0x29,0x26,0x18,0xb6,0xfd + ,0x1c,0x2f,0x8b,0x27,0xff,0x88,0xe0,0xeb] + +alicepk, bobpk, sharedsk :: GroupElement +alicepk = fromJust . decode $ S.pack + [0x85,0x20,0xf0,0x09,0x89,0x30,0xa7,0x54 + ,0x74,0x8b,0x7d,0xdc,0xb4,0x3e,0xf7,0x5a + ,0x0d,0xbf,0x3a,0x0d,0x26,0x38,0x1a,0xf4 + ,0xeb,0xa4,0xa9,0x8e,0xaa,0x9b,0x4e,0x6a] +bobpk = fromJust . decode $ S.pack + [0xde,0x9e,0xdb,0x7d,0x7b,0x7d,0xc1,0xb4 + ,0xd3,0x5b,0x61,0xc2,0xec,0xe4,0x35,0x37 + ,0x3f,0x83,0x43,0xc8,0x5b,0x78,0x67,0x4d + ,0xad,0xfc,0x7e,0x14,0x6f,0x88,0x2b,0x4f] +sharedsk = fromJust . decode $ S.pack + [0x4a,0x5d,0x9d,0x5b,0xa4,0xce,0x2d,0xe1 + ,0x72,0x8e,0x3b,0xf4,0x80,0x35,0x0f,0x25 + ,0xe0,0x7e,0x21,0xc9,0x47,0xd1,0x9e,0x33 + ,0x76,0xf0,0x9b,0x3c,0x1e,0x16,0x17,0x42] + +testScalarMult :: Test +testScalarMult = buildTest $ + return $ testGroup "...Internal.ScalarMult" [ + + testProperty "mult a (multBase a) /= multBase a" + $ \(ByteString32 a') -> + let Just a = decode a' + in mult a (multBase a) /= multBase a, + + testProperty "mult a (multBase b) == mult b (multBase a)" + $ \(ByteString32 a') (ByteString32 b') -> + let Just a = decode a' + Just b = decode b' + in mult a (multBase b) == mult b (multBase a), + + testProperty "matches test vector for alice" + $ multBase alicesk == alicepk, + + testProperty "matches test vector for bob" + $ multBase bobsk == bobpk, + + testProperty "matches test vector for shared secret from alice's view" + $ mult alicesk bobpk == sharedsk, + + testProperty "matches test vector for shared secret from bob's view" + $ mult bobsk alicepk == sharedsk + + ] diff --git a/tests/SealedBoxProperties.hs b/tests/SealedBoxProperties.hs new file mode 100644 index 00000000..41a656dc --- /dev/null +++ b/tests/SealedBoxProperties.hs @@ -0,0 +1,71 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE OverloadedLists #-} + +module SealedBoxProperties ( + testSealedBox +) where + +import Crypto.Saltine.Core.Box +import Data.Monoid +import Test.Framework.Providers.QuickCheck2 +import Test.Framework +import Test.QuickCheck.Property (ioProperty) +import Util + +-- | Ciphertext can be decrypted +rightInverseProp :: Keypair -> Message -> IO Bool +rightInverseProp (Keypair sk1 pk1) (Message bs) = do + enc <- boxSeal pk1 bs + return (Just bs == boxSealOpen pk1 sk1 enc) + +-- | Cannot decrypt without the correct secret key +rightInverseFailureProp1 :: Keypair -> Message -> Perturb -> IO Bool +rightInverseFailureProp1 (Keypair sk1 pk1) (Message bs) p = do + enc <- boxSeal pk1 bs + return (Nothing == boxSealOpen pk1 (perturb sk1 ([0] <> p)) enc) + +-- | Cannot decrypt without the correct public key +rightInverseFailureProp2 :: Keypair -> Message -> Perturb -> IO Bool +rightInverseFailureProp2 (Keypair sk1 pk1) (Message bs) p = do + enc <- boxSeal pk1 bs + return (Nothing == boxSealOpen (perturb pk1 p) sk1 enc) + +-- | Cannot decrypt when not sent to you +rightInverseFailureProp3 :: Keypair -> Message -> Perturb -> IO Bool +rightInverseFailureProp3 (Keypair sk1 pk1) (Message bs) p = do + enc <- boxSeal (perturb pk1 p) bs + return (Nothing == boxSealOpen pk1 sk1 enc) + +-- | Ciphertext cannot be decrypted (verification failure) if the +-- ciphertext is perturbed +rightInverseFailureProp4 :: Keypair -> Message -> Perturb -> IO Bool +rightInverseFailureProp4 (Keypair sk1 pk1) (Message bs) p = do + enc <- boxSeal pk1 bs + return (Nothing == boxSealOpen pk1 sk1 (perturb enc p)) + +testSealedBox :: Test +testSealedBox = buildTest $ do + + kp <- newKeypair + + return $ testGroup "... SealedBox" [ + + testGroup "Can decrypt ciphertext using..." [ + testProperty "... public key/secret key" + $ ioProperty . rightInverseProp kp + ], + + testGroup "Fail to verify ciphertext when..." [ + testProperty "... not using proper secret key" + $ ioProperty . uncurry (rightInverseFailureProp1 kp), + + testProperty "... not using proper public key" + $ ioProperty . uncurry (rightInverseFailureProp2 kp), + + testProperty "... not actually sent to you" + $ ioProperty . uncurry (rightInverseFailureProp3 kp), + + testProperty "... ciphertext has been perturbed" + $ ioProperty . uncurry (rightInverseFailureProp4 kp) + ] + ] diff --git a/tests/SecretBoxProperties.hs b/tests/SecretBoxProperties.hs new file mode 100644 index 00000000..cc9e3eb9 --- /dev/null +++ b/tests/SecretBoxProperties.hs @@ -0,0 +1,116 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module SecretBoxProperties ( + testSecretBox + ) where + +import Util +import Crypto.Saltine.Core.SecretBox +import Crypto.Saltine.Class +import Crypto.Saltine.Internal.SecretBox as Internal + +import qualified Data.ByteString as S +import Data.Maybe (fromJust) +import Test.Framework.Providers.QuickCheck2 +import Test.Framework +import Test.QuickCheck (Property, (==>)) +import Test.QuickCheck.Arbitrary + +instance Arbitrary Nonce where + arbitrary = + do bs <- S.pack <$> vector Internal.secretbox_noncebytes + pure $ fromJust (decode bs) + +instance Arbitrary Key where + arbitrary = + do bs <- S.pack <$> vector Internal.secretbox_keybytes + pure $ fromJust (decode bs) + +-- | Ciphertext can be decrypted +rightInverseProp :: Key -> Nonce -> Message -> Bool +rightInverseProp k n (Message bs) = + Just bs == secretboxOpen k n (secretbox k n bs) + +-- | Detached ciphertext/tag can be decrypted +rightInverseDetachedProp :: Key -> Nonce -> Message -> Bool +rightInverseDetachedProp k n (Message bs) = + Just bs == uncurry (secretboxOpenDetached k n) (secretboxDetached k n bs) + +-- | Ciphertext cannot be decrypted if the ciphertext is perturbed +rightInverseFailureProp :: Key -> Nonce -> Message -> Perturb -> Property +rightInverseFailureProp k n (Message bs) p = + let ct = secretbox k n bs + fakeCT = perturb ct p + in ct /= fakeCT ==> Nothing == secretboxOpen k n fakeCT + +-- | Ciphertext cannot be decrypted if the tag is perturbed +rightInverseTagFailureProp :: Key -> Nonce -> Message -> Message -> Property +rightInverseTagFailureProp k n (Message bs) (Message fakeTagBs) = + let (realTag, ct) = secretboxDetached k n bs + fakeTag = Internal.Au fakeTagBs + in realTag /= fakeTag ==> Nothing == secretboxOpenDetached k n fakeTag ct + +-- | Ciphertext cannot be decrypted if the ciphertext is perturbed +rightInverseFailureDetachedProp :: Key -> Nonce -> Message -> Perturb -> Property +rightInverseFailureDetachedProp k n (Message bs) p = + let (tag,ct) = secretboxDetached k n bs + fakeCT = perturb ct p + in fakeCT /= ct ==> Nothing == secretboxOpenDetached k n tag fakeCT + +-- | Ciphertext cannot be decrypted with a different key +cannotDecryptKeyProp :: Key -> Key -> Nonce -> Message -> Property +cannotDecryptKeyProp k1 k2 n (Message bs) = + k1 /= k2 ==> Nothing == secretboxOpen k2 n (secretbox k1 n bs) + +-- | Ciphertext cannot be decrypted with a different key +cannotDecryptKeyDetachedProp :: Key -> Key -> Nonce -> Message -> Property +cannotDecryptKeyDetachedProp k1 k2 n (Message bs) = + k1 /= k2 ==> Nothing == uncurry (secretboxOpenDetached k2 n) (secretboxDetached k1 n bs) + +-- | Ciphertext cannot be decrypted with a different nonce +cannotDecryptNonceProp :: Key -> Nonce -> Nonce -> Message -> Property +cannotDecryptNonceProp k n1 n2 (Message bs) = + n1 /= n2 ==> Nothing == secretboxOpen k n2 (secretbox k n1 bs) + +-- | Ciphertext cannot be decrypted with a different nonce +cannotDecryptNonceDetachedProp :: Key -> Nonce -> Nonce -> Message -> Property +cannotDecryptNonceDetachedProp k n1 n2 (Message bs) = + n1 /= n2 ==> Nothing == uncurry (secretboxOpenDetached k n2) (secretboxDetached k n1 bs) + +testSecretBox :: Test +testSecretBox = buildTest $ do + + return $ testGroup "...Internal.SecretBox" [ + + testProperty "Can decrypt ciphertext" + $ rightInverseProp, + + testProperty "Can decrypt ciphertext (detached)" + $ rightInverseDetachedProp, + + testGroup "Cannot decrypt ciphertext when..." [ + + testProperty "... ciphertext is perturbed" + $ rightInverseFailureProp, + + testProperty "... ciphertext is perturbed (detached)" + $ rightInverseFailureDetachedProp, + + testProperty "... tag is perturbed (detached)" + $ rightInverseTagFailureProp, + + testProperty "... using the wrong key" + $ cannotDecryptKeyProp, + + testProperty "... using the wrong key (detached)" + $ cannotDecryptKeyDetachedProp, + + testProperty "... using the wrong nonce" + $ cannotDecryptNonceProp, + + testProperty "... using the wrong nonce (detached" + $ cannotDecryptNonceDetachedProp + + ] + ] diff --git a/tests/SignProperties.hs b/tests/SignProperties.hs new file mode 100644 index 00000000..56a72055 --- /dev/null +++ b/tests/SignProperties.hs @@ -0,0 +1,43 @@ +{-# LANGUAGE OverloadedStrings #-} + +module SignProperties ( + testSign + ) where + +import Util +import Crypto.Saltine.Core.Sign +import Crypto.Saltine.Internal.Sign + +import qualified Data.ByteString as S +import Test.Framework.Providers.QuickCheck2 +import Test.Framework +import Test.QuickCheck + +testSign :: Test +testSign = buildTest $ do + kp1 <- newKeypair + let sk1 = secretKey kp1 + let pk1 = publicKey kp1 + kp2 <- newKeypair + let pk2 = publicKey kp2 + + return $ testGroup "...Internal.Sign" [ + + testProperty "Verifies signed message" + $ \(Message bs) -> signOpen pk1 (sign sk1 bs) == Just bs, + + testProperty "Verifies signed message w/ detached signature" + $ \(Message bs) -> signVerifyDetached pk1 (signDetached sk1 bs) bs, + + testProperty "Signed message longer than message" + $ \(Message bs) -> S.length (sign sk1 bs) >= S.length bs, + + testProperty "Rejects message with mismatched key" + $ \(Message bs) -> not (S.null bs) ==> + signOpen pk2 (sign sk1 bs) == Nothing, + + testProperty "Rejects message with mismatched key w/ detached signature" + $ \(Message bs) -> not (S.null bs) ==> + not (signVerifyDetached pk2 (signDetached sk1 bs) bs) + + ] diff --git a/tests/StreamProperties.hs b/tests/StreamProperties.hs new file mode 100644 index 00000000..75ad4d67 --- /dev/null +++ b/tests/StreamProperties.hs @@ -0,0 +1,32 @@ +{-# LANGUAGE OverloadedStrings #-} + +module StreamProperties ( + testStream + ) where + +import Util +import Crypto.Saltine.Core.Stream + +import qualified Data.ByteString as S +import Test.Framework.Providers.QuickCheck2 +import Test.Framework +import Test.QuickCheck + +testStream :: Test +testStream = buildTest $ do + k <- newKey + n <- newNonce + return $ testGroup "...Internal.Stream" [ + + testProperty "Stream is apropriately sized" + $ \len -> (len > 0 && len < 200) + ==> S.length (stream k n len) == len, + + testProperty "xor munges input" + $ \(Message bs) -> not (S.null bs) + ==> xor k n bs /= bs, + + testProperty "xor is involutive" + $ \(Message bs) -> xor k n (xor k n bs) == bs + + ] diff --git a/tests/Util.hs b/tests/Util.hs new file mode 100644 index 00000000..b729fa53 --- /dev/null +++ b/tests/Util.hs @@ -0,0 +1,55 @@ +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} +module Util where + +import Crypto.Saltine.Class + +import Control.Monad (replicateM) +import qualified Data.ByteString as S +import Data.Monoid +import Data.Semigroup (Semigroup) +import Data.Word (Word8) +import Data.Bits (xor) +import Test.QuickCheck +import GHC.Exts (IsList(..)) + +instance IsEncoding S.ByteString where + encode x = x + decode x = Just x + +perturb :: IsEncoding a => a -> Perturb -> a +perturb a (Perturb p) = + let bytes = encode a + len = S.length bytes + plen = length p + fullP = p <> replicate (len - plen) 0 + newBytes = S.pack $ zipWith xor fullP (S.unpack bytes) + in case decode newBytes of + Nothing -> error "Invalid use of perturb on picky encoding." + Just x -> x + +newtype Perturb = Perturb [Word8] + deriving (Show,Semigroup,Monoid) + +instance IsList Perturb where + type Item Perturb = Word8 + fromList = Perturb + toList (Perturb x) = x + +instance Arbitrary Perturb where + arbitrary = + do bs <- arbitrary + if all (==0) bs + then pure (Perturb (1:bs)) + else pure (Perturb bs) + +newtype ByteString32 = ByteString32 S.ByteString deriving (Eq,Show) + +instance Arbitrary ByteString32 where + arbitrary = ByteString32 . S.pack <$> replicateM 32 arbitrary + +newtype Message = Message S.ByteString deriving (Show) + +instance Arbitrary Message where + arbitrary = Message . S.pack <$> arbitrary diff --git a/tests/UtilProperties.hs b/tests/UtilProperties.hs new file mode 100644 index 00000000..01cad763 --- /dev/null +++ b/tests/UtilProperties.hs @@ -0,0 +1,28 @@ +{-# LANGUAGE OverloadedStrings #-} + +module UtilProperties ( + testUtils + ) where + +import Test.Framework.Providers.QuickCheck2 +import Test.Framework +import Test.QuickCheck +import Util + +import qualified Crypto.Saltine.Internal.Util as U + +-- | Testing the comparison of keys +keyEquality :: ByteString32 -> Property +keyEquality k@(ByteString32 bs1) = k === k .&. U.compare bs1 bs1 + +keyInequality :: ByteString32 -> ByteString32 -> Property +keyInequality k1@(ByteString32 bs1) k2@(ByteString32 bs2) = + k1 /= k2 ==> not $ U.compare bs1 bs2 + + +testUtils :: Test +testUtils = buildTest $ do + return $ testGroup "...Utils" [ + testProperty "ByteString equality" keyEquality, + testProperty "ByteString inequality" keyInequality + ]