NaCl 0.0.2.0 → 0.0.3.0
raw patch · 6 files changed
+387/−24 lines, 6 filesdep −gdpdep ~libsodiumPVP ok
version bump matches the API change (PVP)
Dependencies removed: gdp
Dependency ranges changed: libsodium
API changes (from Hackage documentation)
+ Crypto.Box: create :: (ByteArrayAccess nonceBytes, ByteArrayAccess ptBytes, ByteArray ctBytes) => PublicKey -> SecretKey -> Nonce nonceBytes -> ptBytes -> ctBytes
+ Crypto.Box: keypair :: IO (PublicKey, SecretKey)
+ Crypto.Box: open :: (ByteArrayAccess nonceBytes, ByteArray ptBytes, ByteArrayAccess ctBytes) => SecretKey -> PublicKey -> Nonce nonceBytes -> ctBytes -> Maybe ptBytes
+ Crypto.Box: toNonce :: ByteArrayAccess ba => ba -> Maybe (Nonce ba)
+ Crypto.Box: toPublicKey :: ByteString -> Maybe PublicKey
+ Crypto.Box: toSecretKey :: ScrubbedBytes -> Maybe SecretKey
+ Crypto.Box: type Nonce a = SizedByteArray CRYPTO_BOX_NONCEBYTES a
+ Crypto.Box: type PublicKey = SizedByteArray CRYPTO_BOX_PUBLICKEYBYTES ByteString
+ Crypto.Box: type SecretKey = SizedByteArray CRYPTO_BOX_SECRETKEYBYTES ScrubbedBytes
+ Crypto.Box.Internal: create :: (ByteArrayAccess nonce, ByteArrayAccess pt, ByteArray ct) => PublicKey -> SecretKey -> Nonce nonce -> pt -> IO ct
+ Crypto.Box.Internal: keypair :: IO (PublicKey, SecretKey)
+ Crypto.Box.Internal: open :: (ByteArrayAccess nonce, ByteArray pt, ByteArrayAccess ct) => SecretKey -> PublicKey -> Nonce nonce -> ct -> IO (Maybe pt)
+ Crypto.Box.Internal: toNonce :: ByteArrayAccess ba => ba -> Maybe (Nonce ba)
+ Crypto.Box.Internal: toPublicKey :: ByteString -> Maybe PublicKey
+ Crypto.Box.Internal: toSecretKey :: ScrubbedBytes -> Maybe SecretKey
+ Crypto.Box.Internal: type Nonce a = SizedByteArray CRYPTO_BOX_NONCEBYTES a
+ Crypto.Box.Internal: type PublicKey = SizedByteArray CRYPTO_BOX_PUBLICKEYBYTES ByteString
+ Crypto.Box.Internal: type SecretKey = SizedByteArray CRYPTO_BOX_SECRETKEYBYTES ScrubbedBytes
Files
- CHANGELOG.md +1/−0
- NaCl.cabal +20/−15
- lib/Crypto/Box.hs +108/−0
- lib/Crypto/Box/Internal.hs +138/−0
- lib/Crypto/Secretbox.hs +7/−9
- test/Test/Crypto/Box.hs +113/−0
CHANGELOG.md view
@@ -6,3 +6,4 @@ * `Sized` byte arrays * `Secretbox`+* `Box`
NaCl.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 03e840884f33f5935cba7897a47476ae30b4577c0d482ca7f4d75cdad144e7c1+-- hash: 02b4db73a797bd9fea66be22ae988908a6417d2d841dcce7d53ecba29b09c985 name: NaCl-version: 0.0.2.0+version: 0.0.3.0 synopsis: Easy-and-safe-to-use high-level Haskell bindings to NaCl description: This library uses <https://libsodium.org Sodium> under the hood, but only exposes the primitives that are part of the “classic” NaCl@@ -15,12 +15,6 @@ . __Note: this package is experimental and WIP.__ .- = Secret-key cryptography- .- * Authenticated encryption: "Crypto.Secretbox"- .- = Additional primitives- . Sodium is more portable, but some people prefer to stick to NaCl. We agree that it is better to be paranoid than sorry. That is why, even though this library uses@@ -30,8 +24,15 @@ Sodium provides useful algorithms, that are not part of NaCl, for example key derivation or random number generation. If you need them too (you probably do), you should use- <https://hackage.haskell.org/package/crypto-sodium crypto-sodium>- instead.+ </package/crypto-sodium crypto-sodium> instead.+ .+ = Secret-key cryptography+ .+ * Authenticated encryption: "Crypto.Secretbox"+ .+ = Public-key cryptography+ .+ * Authenticated encryption: "Crypto.Box" category: Cryptography homepage: https://github.com/serokell/haskell-crypto#readme bug-reports: https://github.com/serokell/haskell-crypto/issues@@ -51,19 +52,20 @@ library exposed-modules:+ Crypto.Box+ Crypto.Box.Internal Crypto.Secretbox Crypto.Secretbox.Internal other-modules: Paths_NaCl hs-source-dirs: lib- default-extensions: DataKinds FlexibleContexts FlexibleInstances GeneralizedNewtypeDeriving KindSignatures MultiParamTypeClasses NumericUnderscores OverloadedStrings PolyKinds ScopedTypeVariables+ default-extensions: DataKinds FlexibleContexts FlexibleInstances GeneralizedNewtypeDeriving KindSignatures LambdaCase MultiParamTypeClasses NamedFieldPuns NumericUnderscores OverloadedStrings PolyKinds ScopedTypeVariables TypeApplications ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints build-depends: base >=4.10 && <4.15 , bytestring >=0.9 && <0.11- , gdp >=0.0.0.1 && <0.1- , libsodium >=1.0 && <2+ , libsodium >=1.0.11 && <2 , memory >=0.14.15 && <0.16 , safe-exceptions >=0.1 && <0.2 default-language: Haskell2010@@ -72,11 +74,12 @@ type: exitcode-stdio-1.0 main-is: Test.hs other-modules:+ Test.Crypto.Box Test.Crypto.Secretbox Paths_NaCl hs-source-dirs: test- default-extensions: DataKinds FlexibleContexts FlexibleInstances GeneralizedNewtypeDeriving KindSignatures MultiParamTypeClasses NumericUnderscores OverloadedStrings PolyKinds ScopedTypeVariables+ default-extensions: DataKinds FlexibleContexts FlexibleInstances GeneralizedNewtypeDeriving KindSignatures LambdaCase MultiParamTypeClasses NamedFieldPuns NumericUnderscores OverloadedStrings PolyKinds ScopedTypeVariables TypeApplications ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints build-tool-depends: tasty-discover:tasty-discover@@ -86,7 +89,9 @@ , base >=4.10 && <4.15 , bytestring >=0.9 && <0.11 , hedgehog- , libsodium+ , libsodium >=1.0.11 && <2+ , memory >=0.14.15 && <0.16+ , safe-exceptions >=0.1 && <0.2 , tasty , tasty-hedgehog , tasty-hunit
+ lib/Crypto/Box.hs view
@@ -0,0 +1,108 @@+-- SPDX-FileCopyrightText: 2020 Serokell+--+-- SPDX-License-Identifier: MPL-2.0++-- | Public-key authenticated encryption.+--+-- It is best to import this module qualified:+--+-- @+-- import qualified Crypto.Box as Box+--+-- encrypted = Box.'create' pk sk nonce message+-- decrypted = Box.'open' pk sk nonce encrypted+-- @+--+-- This is @crypto_box_*@ from NaCl.+module Crypto.Box+ ( PublicKey+ , toPublicKey+ , SecretKey+ , toSecretKey+ , keypair++ , Nonce+ , toNonce++ , create+ , open+ ) where++import Data.ByteArray (ByteArray, ByteArrayAccess)+import System.IO.Unsafe (unsafeDupablePerformIO)++import Crypto.Box.Internal (Nonce, PublicKey, SecretKey, keypair, toNonce, toPublicKey, toSecretKey)++import qualified Crypto.Box.Internal as I+++-- | Encrypt a message.+--+-- @+-- encrypted = Box.create pk sk nonce message+-- @+--+-- * @pk@ is the receiver’s public key, used for encryption.+-- @sk@ is the sender’s public key, used for authentication.+--+-- These are generated using 'keypair' and are supposed to be exchanged+-- in advance. Both parties need to know their own secret key and the other’s+-- public key.+--+-- * @nonce@ is an extra noise that ensures that if you encrypt the same+-- message with the same key multiple times, you will get different ciphertexts,+-- which is required for+-- <https://en.wikipedia.org/wiki/Semantic_security semantic security>.+-- There are two standard ways of getting it:+--+-- 1. /Use a counter/. In this case you keep a counter of encrypted messages,+-- which means that the nonce will be new for each new message.+--+-- 2. /Random/. You generate a random nonce every time you encrypt a message.+-- Since the nonce is large enough, the chances of you using the same+-- nonce twice are negligible. For useful helpers, see @Crypto.Random@,+-- in <https://hackage.haskell.org/package/crypto-sodium crypto-sodium>.+--+-- * @message@ is the data you are encrypting.+--+-- This function adds authentication data, so if anyone modifies the cyphertext,+-- @open@ will refuse to decrypt it.+create+ :: ( ByteArrayAccess nonceBytes+ , ByteArrayAccess ptBytes, ByteArray ctBytes+ )+ => PublicKey -- ^ Receiver’s public key+ -> SecretKey -- ^ Sender’s secret key+ -> Nonce nonceBytes -- ^ Nonce+ -> ptBytes -- ^ Plaintext message+ -> ctBytes+create pk sk nonce msg =+ -- This IO is safe, because it is pure.+ unsafeDupablePerformIO $ I.create pk sk nonce msg+++-- | Decrypt a message.+--+-- @+-- decrypted = Box.open sk pk nonce encrypted+-- @+--+-- * @sk@ is the receiver’s secret key, used for description.+-- * @pk@ is the sender’s public key, used for authentication.+-- * @nonce@ is the same that was used for encryption.+-- * @encrypted@ is the output of 'create'.+--+-- This function will return @Nothing@ if the encrypted message was tampered+-- with after it was encrypted.+open+ :: ( ByteArrayAccess nonceBytes+ , ByteArray ptBytes, ByteArrayAccess ctBytes+ )+ => SecretKey -- ^ Receiver’s secret key+ -> PublicKey -- ^ Sender’s public key+ -> Nonce nonceBytes -- ^ Nonce+ -> ctBytes -- ^ Encrypted message (cyphertext)+ -> Maybe ptBytes+open sk pk nonce ct =+ -- This IO is safe, because it is pure.+ unsafeDupablePerformIO $ I.open sk pk nonce ct
+ lib/Crypto/Box/Internal.hs view
@@ -0,0 +1,138 @@+-- SPDX-FileCopyrightText: 2020 Serokell+--+-- SPDX-License-Identifier: MPL-2.0++-- | Internals of @crypto_box@.+module Crypto.Box.Internal+ ( SecretKey+ , toSecretKey+ , PublicKey+ , toPublicKey+ , keypair++ , Nonce+ , toNonce++ , create+ , open+ ) where++import Prelude hiding (length)++import Data.ByteArray (ByteArray, ByteArrayAccess, ScrubbedBytes, allocRet, length, withByteArray)+import Data.ByteArray.Sized (SizedByteArray, sizedByteArray)+import Data.ByteString (ByteString)+import Data.Functor (void)+import Data.Proxy (Proxy (Proxy))++import qualified Data.ByteArray.Sized as Sized (alloc, allocRet)+import qualified Libsodium as Na+++-- | Secret key that can be used for Box.+type SecretKey = SizedByteArray Na.CRYPTO_BOX_SECRETKEYBYTES ScrubbedBytes++-- | Convert bytes to a secret key.+toSecretKey :: ScrubbedBytes -> Maybe SecretKey+toSecretKey = sizedByteArray++-- | Public key that can be used for Box.+type PublicKey = SizedByteArray Na.CRYPTO_BOX_PUBLICKEYBYTES ByteString++toPublicKey :: ByteString -> Maybe PublicKey+toPublicKey = sizedByteArray++-- | Generate a new 'SecretKey' together with its 'PublicKey'.+--+-- Note: this function is not thread-safe (since the underlying+-- C function is not thread-safe both in Sodium and in NaCl)!+-- Either make sure there are no concurrent calls or see+-- @Crypto.Init@ in+-- <https://hackage.haskell.org/package/crypto-sodium crypto-sodium>+-- to learn how to make this function thread-safe.+keypair :: IO (PublicKey, SecretKey)+keypair = do+ (pk, sk) <-+ Sized.allocRet Proxy $ \skPtr ->+ Sized.alloc $ \pkPtr ->+ -- always returns 0, so we don’t check it+ void $ Na.crypto_box_keypair pkPtr skPtr+ pure (pk, sk)+++-- | Nonce that can be used for Box.+--+-- This type is parametrised by the actual data type that contains+-- bytes. This can be, for example, a @ByteString@.+type Nonce a = SizedByteArray Na.CRYPTO_BOX_NONCEBYTES a++-- | Make a 'Nonce' from an arbitrary byte array.+--+-- This function returns @Just@ if and only if the byte array has+-- the right length to be used as a nonce with a Box.+toNonce :: ByteArrayAccess ba => ba -> Maybe (Nonce ba)+toNonce = sizedByteArray+++-- | Encrypt a message.+create+ :: ( ByteArrayAccess nonce+ , ByteArrayAccess pt, ByteArray ct+ )+ => PublicKey -- ^ Receiver’s public key+ -> SecretKey -- ^ Sender’s secret key+ -> Nonce nonce -- ^ Nonce+ -> pt -- ^ Plaintext message+ -> IO ct+create pk sk nonce msg = do+ (_ret, ct) <-+ allocRet clen $ \ctPtr ->+ withByteArray pk $ \pkPtr ->+ withByteArray sk $ \skPtr ->+ withByteArray nonce $ \noncePtr ->+ withByteArray msg $ \msgPtr -> do+ -- TODO: Maybe, reimplement this without _easy, to stay closer+ -- to the original NaCl.+ Na.crypto_box_easy ctPtr+ msgPtr (fromIntegral $ length msg)+ noncePtr+ pkPtr skPtr+ -- _ret can be only 0, so we don’t check it+ -- TODO: Actually, it looks like this function can fail and return+ -- a -1, even though this is not documented :/.+ pure ct+ where+ clen :: Int+ clen = fromIntegral Na.crypto_box_macbytes + length msg+++-- | Decrypt a message.+open+ :: ( ByteArrayAccess nonce+ , ByteArray pt, ByteArrayAccess ct+ )+ => SecretKey -- ^ Receiver’s secret key+ -> PublicKey -- ^ Sender’s public key+ -> Nonce nonce -- ^ Nonce+ -> ct -- ^ Cyphertext+ -> IO (Maybe pt)+open sk pk nonce ct = do+ (ret, msg) <-+ allocRet mlen $ \msgPtr ->+ withByteArray sk $ \skPtr ->+ withByteArray pk $ \pkPtr ->+ withByteArray nonce $ \noncePtr ->+ withByteArray ct $ \ctPtr -> do+ -- TODO: Maybe, reimplement this without _easy, to stay closer+ -- to the original NaCl.+ Na.crypto_box_open_easy msgPtr+ ctPtr (fromIntegral $ length ct)+ noncePtr+ pkPtr skPtr+ if ret == 0 then+ pure $ Just msg+ else+ pure Nothing+ where+ mlen :: Int+ mlen = length ct - fromIntegral Na.crypto_box_macbytes
lib/Crypto/Secretbox.hs view
@@ -46,15 +46,15 @@ -- you must use a -- <https://en.wikipedia.org/wiki/Key_derivation_function key derivation function> -- to turn this password into an encryption key.--- NaCl does not provide a key derivation function, however--- <https://hackage.haskell.org/package/crypto-sodium crypto-sodium> does. -- -- 2. /Generate a random one/. This can be useful in certain situations when -- you want to have an intermediate key that you will encrypt and share--- later. NaCl does not provide a cryptographically secure pseudo random--- generator, however--- <https://hackage.haskell.org/package/crypto-sodium crypto-sodium> does.+-- later. --+-- The @Crypto.Key@ module in+-- <https://hackage.haskell.org/package/crypto-sodium crypto-sodium>+-- has functions to help in either case.+-- -- * @nonce@ is an extra noise that ensures that if you encrypt the same -- message with the same key multiple times, you will get different ciphertexts, -- which is required for@@ -66,10 +66,8 @@ -- -- 2. /Random/. You generate a random nonce every time you encrypt a message. -- Since the nonce is large enough, the chances of you using the same--- nonce twice are negligible.------ <https://hackage.haskell.org/package/crypto-sodium crypto-sodium> has--- useful helpers for both options.+-- nonce twice are negligible. For useful helpers, see @Crypto.Random@,+-- in <https://hackage.haskell.org/package/crypto-sodium crypto-sodium>. -- -- * @message@ is the data you are encrypting. --
+ test/Test/Crypto/Box.hs view
@@ -0,0 +1,113 @@+-- SPDX-FileCopyrightText: 2020 Serokell+--+-- SPDX-License-Identifier: MPL-2.0++{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}++module Test.Crypto.Box where++import Hedgehog (Property, forAll, property, tripping)+import Hedgehog.Internal.Property (forAllT)+import Test.HUnit ((@?=), Assertion)++import Data.ByteArray (ScrubbedBytes, convert)+import Data.ByteString (ByteString)+import Control.Monad.IO.Class (liftIO)++import qualified Data.ByteString as BS+import qualified Libsodium as Na++import qualified Hedgehog.Gen as G+import qualified Hedgehog.Range as R++import qualified Crypto.Box as Box+++nonceSize :: R.Range Int+nonceSize = R.singleton $ fromIntegral Na.crypto_box_noncebytes+++hprop_encode_decode :: Property+hprop_encode_decode = property $ do+ (pkS, skS) <- forAllT $ liftIO $ Box.keypair+ (pkR, skR) <- forAllT $ liftIO $ Box.keypair+ nonceBytes <- forAll $ G.bytes nonceSize+ let Just nonce = Box.toNonce nonceBytes+ msg <- forAll $ G.bytes (R.linear 0 1_000)+ tripping msg (encodeBs pkR skS nonce) (decodeBs skR pkS nonce)+ where+ -- We need to specify the type of the cyphertext as it is polymorphic+ encodeBs pkR skS nonce msg = Box.create pkR skS nonce msg :: ByteString+ decodeBs skR pkS nonce ct = Box.open skR pkS nonce ct :: Maybe ByteString+++-- Test vector from+-- https://github.com/jedisct1/libsodium/blob/f911b56650b680ecfc5d32b11b090849fc2b5f92/test/default/box.c++exampleSk :: ScrubbedBytes+exampleSk = convert $ BS.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+ ]++examplePk :: ByteString+examplePk = BS.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+ ]++exampleNonce :: ByteString+exampleNonce = BS.pack $+ [ 0x69, 0x69, 0x6e, 0xe9, 0x55, 0xb6+ , 0x2b, 0x73, 0xcd, 0x62, 0xbd, 0xa8+ , 0x75, 0xfc, 0x73, 0xd6, 0x82, 0x19+ , 0xe0, 0x03, 0x6b, 0x7a, 0x0b, 0x37+ ]++exampleMsg :: ByteString+exampleMsg = BS.pack $+ [ 0xbe, 0x07, 0x5f, 0xc5+ , 0x3c, 0x81, 0xf2, 0xd5, 0xcf, 0x14, 0x13, 0x16, 0xeb, 0xeb, 0x0c, 0x7b+ , 0x52, 0x28, 0xc5, 0x2a, 0x4c, 0x62, 0xcb, 0xd4, 0x4b, 0x66, 0x84, 0x9b+ , 0x64, 0x24, 0x4f, 0xfc, 0xe5, 0xec, 0xba, 0xaf, 0x33, 0xbd, 0x75, 0x1a+ , 0x1a, 0xc7, 0x28, 0xd4, 0x5e, 0x6c, 0x61, 0x29, 0x6c, 0xdc, 0x3c, 0x01+ , 0x23, 0x35, 0x61, 0xf4, 0x1d, 0xb6, 0x6c, 0xce, 0x31, 0x4a, 0xdb, 0x31+ , 0x0e, 0x3b, 0xe8, 0x25, 0x0c, 0x46, 0xf0, 0x6d, 0xce, 0xea, 0x3a, 0x7f+ , 0xa1, 0x34, 0x80, 0x57, 0xe2, 0xf6, 0x55, 0x6a, 0xd6, 0xb1, 0x31, 0x8a+ , 0x02, 0x4a, 0x83, 0x8f, 0x21, 0xaf, 0x1f, 0xde, 0x04, 0x89, 0x77, 0xeb+ , 0x48, 0xf5, 0x9f, 0xfd, 0x49, 0x24, 0xca, 0x1c, 0x60, 0x90, 0x2e, 0x52+ , 0xf0, 0xa0, 0x89, 0xbc, 0x76, 0x89, 0x70, 0x40, 0xe0, 0x82, 0xf9, 0x37+ , 0x76, 0x38, 0x48, 0x64, 0x5e, 0x07, 0x05+ ]++exampleCt :: ByteString+exampleCt = BS.pack $+ [ 0xf3, 0xff, 0xc7, 0x70, 0x3f, 0x94, 0x00, 0xe5+ , 0x2a, 0x7d, 0xfb, 0x4b, 0x3d, 0x33, 0x05, 0xd9+ , 0x8e, 0x99, 0x3b, 0x9f, 0x48, 0x68, 0x12, 0x73+ , 0xc2, 0x96, 0x50, 0xba, 0x32, 0xfc, 0x76, 0xce+ , 0x48, 0x33, 0x2e, 0xa7, 0x16, 0x4d, 0x96, 0xa4+ , 0x47, 0x6f, 0xb8, 0xc5, 0x31, 0xa1, 0x18, 0x6a+ , 0xc0, 0xdf, 0xc1, 0x7c, 0x98, 0xdc, 0xe8, 0x7b+ , 0x4d, 0xa7, 0xf0, 0x11, 0xec, 0x48, 0xc9, 0x72+ , 0x71, 0xd2, 0xc2, 0x0f, 0x9b, 0x92, 0x8f, 0xe2+ , 0x27, 0x0d, 0x6f, 0xb8, 0x63, 0xd5, 0x17, 0x38+ , 0xb4, 0x8e, 0xee, 0xe3, 0x14, 0xa7, 0xcc, 0x8a+ , 0xb9, 0x32, 0x16, 0x45, 0x48, 0xe5, 0x26, 0xae+ , 0x90, 0x22, 0x43, 0x68, 0x51, 0x7a, 0xcf, 0xea+ , 0xbd, 0x6b, 0xb3, 0x73, 0x2b, 0xc0, 0xe9, 0xda+ , 0x99, 0x83, 0x2b, 0x61, 0xca, 0x01, 0xb6, 0xde+ , 0x56, 0x24, 0x4a, 0x9e, 0x88, 0xd5, 0xf9, 0xb3+ , 0x79, 0x73, 0xf6, 0x22, 0xa4, 0x3d, 0x14, 0xa6+ , 0x59, 0x9b, 0x1f, 0x65, 0x4c, 0xb4, 0x5a, 0x74+ , 0xe3, 0x55, 0xa5+ ]++unit_example_create :: Assertion+unit_example_create = do+ let Just sk = Box.toSecretKey exampleSk+ let Just pk = Box.toPublicKey examplePk+ let Just nonce = Box.toNonce exampleNonce+ Box.create pk sk nonce exampleMsg @?= exampleCt