Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GHC 9.6 compatibility by forking more packages #5

Open
ysangkok opened this issue Jun 12, 2023 · 7 comments
Open

GHC 9.6 compatibility by forking more packages #5

ysangkok opened this issue Jun 12, 2023 · 7 comments

Comments

@ysangkok
Copy link

ysangkok commented Jun 12, 2023

Since crypton is a new package, I was wondering whether it doesn't make sense to make big changes now, which is early in its development cycle. That would include stuff like e.g. changes in central dependencies, like basement and foundation.

It seems inconsistent to me to simply fork crypton off, while leaving the dependency on basement intact. Foundation is also suffering from lack of maintenance, and these issues could also affect crypton and warp.

For example, there is a bug in rotateL for Word256 with no reply from the maintainer:

And there is this incompatibility with GHC 9.6, which seems fairly foundational (excuse my pun). Since Vincent disagrees with the direction of GHC, it seems he refuses to make it compatible with newer GHC versions. Since crypton is a new fork, it would make sense to offer compatiblity with newer GHC. That way, another fork could be avoided. Would be nice if we could minimize the amount of forking.

EDIT: Note that the above incompatibility has been addressed, and that foundation-0.0.30 is in fact compatible with GHC 9.6 even though the linked issue was not marked as fixed.

@kazu-yamamoto
Copy link
Owner

  • Long plan: preparing good bindings to crypto functions in a good crypto library (e.g. OpenSSL) and throw awary crypton OR keep APIs but change the internal drastically to depend on the bindngs
  • Middle plan: removing dependencies to memory and foundation
  • Short plan: forking memory and foundation if the middle plain would take time

This is what in my mind.

@ysangkok
Copy link
Author

If we remove dependencies on memory and foundation , we need another source for constant-time comparisons and comparisons to an all-zero string. It seems to be available in the libsodium bindings, but I am not sure whether that is the best approach? If you are strictly aiming to reduce the amount of dependencies, it wouldn't be possible.

Here is what I have so far, do you like this approach?

diff --git a/Crypto/Cipher/Types/Utils.hs b/Crypto/Cipher/Types/Utils.hs
index 1185404..06e45d0 100644
--- a/Crypto/Cipher/Types/Utils.hs
+++ b/Crypto/Cipher/Types/Utils.hs
@@ -9,11 +9,10 @@
 --
 module Crypto.Cipher.Types.Utils where
 
-import           Crypto.Internal.ByteArray (ByteArray)
-import qualified Crypto.Internal.ByteArray as B
+import qualified Data.ByteString as B
 
 -- | Chunk some input byte array into @sz byte list of byte array.
-chunk :: ByteArray b => Int -> b -> [b]
+chunk :: Int -> B.ByteString -> [B.ByteString]
 chunk sz bs = split bs
   where split b | B.length b <= sz = [b]
                 | otherwise        =
diff --git a/Crypto/Data/Padding.hs b/Crypto/Data/Padding.hs
index 902b132..e2735b8 100644
--- a/Crypto/Data/Padding.hs
+++ b/Crypto/Data/Padding.hs
@@ -14,8 +14,13 @@ module Crypto.Data.Padding
     , unpad
     ) where
 
-import           Data.ByteArray (ByteArray, Bytes)
-import qualified Data.ByteArray as B
+import           Data.ByteString (ByteString)
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Unsafe as BU
+import           Foreign.Ptr (castPtr)
+import           Foreign.C.Types (CInt)
+import           System.IO.Unsafe (unsafePerformIO)
+import           LibSodium.Bindings.Comparison (sodiumMemcmp)
 
 -- | Format of padding
 data Format =
@@ -25,7 +30,7 @@ data Format =
     deriving (Show, Eq)
 
 -- | Apply some pad to a bytearray
-pad :: ByteArray byteArray => Format -> byteArray -> byteArray
+pad :: Format -> ByteString -> ByteString
 pad  PKCS5     bin = pad (PKCS7 8) bin
 pad (PKCS7 sz) bin = bin `B.append` paddingString
   where
@@ -41,21 +46,26 @@ pad (ZERO sz)  bin = bin `B.append` paddingString
     m = len `mod` sz
     len = B.length bin
 
+constTimingCompare :: ByteString -> ByteString -> CInt
+constTimingCompare p1 p2 = unsafePerformIO $ BU.unsafeUseAsCStringLen p1 $ \(c1, l1) ->
+  BU.unsafeUseAsCStringLen p2 $ \(c2, l2) ->
+    pure $ sodiumMemcmp (castPtr c1) (castPtr c2) (fromIntegral l1)
+
 -- | Try to remove some padding from a bytearray.
-unpad :: ByteArray byteArray => Format -> byteArray -> Maybe byteArray
+unpad :: Format -> ByteString -> Maybe ByteString
 unpad  PKCS5     bin = unpad (PKCS7 8) bin
 unpad (PKCS7 sz) bin
     | len == 0                           = Nothing
     | (len `mod` sz) /= 0                = Nothing
     | paddingSz < 1 || paddingSz > len   = Nothing
-    | paddingWitness `B.constEq` padding = Just content
+    | constTimingCompare paddingWitness padding == 0 = Just content
     | otherwise                          = Nothing
   where
     len         = B.length bin
     paddingByte = B.index bin (len - 1)
     paddingSz   = fromIntegral paddingByte
     (content, padding) = B.splitAt (len - paddingSz) bin
-    paddingWitness     = B.replicate paddingSz paddingByte :: Bytes
+    paddingWitness     = B.replicate paddingSz paddingByte
 unpad (ZERO sz)  bin
     | len == 0                           = Nothing
     | (len `mod` sz) /= 0                = Nothing
diff --git a/Crypto/Internal/ByteArray.hs b/Crypto/Internal/ByteArray.hs
index 57ab57a..592ceb2 100644
--- a/Crypto/Internal/ByteArray.hs
+++ b/Crypto/Internal/ByteArray.hs
@@ -10,30 +10,18 @@
 {-# LANGUAGE BangPatterns #-}
 {-# OPTIONS_HADDOCK hide #-}
 module Crypto.Internal.ByteArray
-    ( module Data.ByteArray
-    , module Data.ByteArray.Mapping
-    , module Data.ByteArray.Encoding
-    , constAllZero
+    ( constAllZero
     ) where
 
-import Data.ByteArray
-import Data.ByteArray.Mapping
-import Data.ByteArray.Encoding
+import Data.ByteString
+import Data.ByteString.Unsafe
 
-import Data.Bits ((.|.))
-import Data.Word (Word8)
-import Foreign.Ptr (Ptr)
-import Foreign.Storable (peekByteOff)
+import LibSodium.Bindings.Comparison
+
+import Foreign.Ptr (castPtr)
 
 import Crypto.Internal.Compat (unsafeDoIO)
 
-constAllZero :: ByteArrayAccess ba => ba -> Bool
-constAllZero b = unsafeDoIO $ withByteArray b $ \p -> loop p 0 0
-  where
-    loop :: Ptr b -> Int -> Word8 -> IO Bool
-    loop p i !acc
-        | i == len  = return $! acc == 0
-        | otherwise = do
-            e <- peekByteOff p i
-            loop p (i+1) (acc .|. e)
-    len = Data.ByteArray.length b
+constAllZero :: ByteString -> Bool
+constAllZero b = unsafeDoIO $ unsafeUseAsCStringLen b $ \(p1, l1) ->
+  pure $ sodiumIsZero (castPtr p1) (fromIntegral l1) == 1
diff --git a/crypton.cabal b/crypton.cabal
index eec79fe..b0b313d 100644
--- a/crypton.cabal
+++ b/crypton.cabal
@@ -251,8 +251,7 @@ Library
     Build-depends:   base
 
   Build-depends:     bytestring
-                   , memory >= 0.14.18
-                   , basement >= 0.0.6
+                   , libsodium-bindings
                    , ghc-prim
   ghc-options:       -Wall -fwarn-tabs -optc-O3
   if os(linux)
@@ -462,7 +461,6 @@ Test-Suite test-crypton
                      XSalsa
   Build-Depends:     base >= 0 && < 10
                    , bytestring
-                   , memory
                    , tasty
                    , tasty-quickcheck
                    , tasty-hunit
@@ -479,7 +477,6 @@ Benchmark bench-crypton
   Build-Depends:     base
                    , bytestring
                    , deepseq
-                   , memory
                    , gauge
                    , random
                    , crypton

@kazu-yamamoto
Copy link
Owner

Here is what I have so far, do you like this approach?

Yes. This is exactly the same as my middle plan.

@kazu-yamamoto
Copy link
Owner

If we remove dependencies on memory and foundation , we need another source for constant-time comparisons and comparisons to an all-zero string.

I don't know this area well. Doesn't bytestring provide such features?

@exarkun
Copy link

exarkun commented Sep 29, 2023

If we remove dependencies on memory and foundation , we need another source for constant-time comparisons and comparisons to an all-zero string.

I don't know this area well. Doesn't bytestring provide such features?

It doesn't provide a constant-time comparison function. The Eq instance for strict ByteString comes down to memcmp from the underlying C library. A constant-time comparison function is necessary to avoid leaking timing information to an observer. Timing operations on secrets can result in a side-channel for exfiltrating those secrets.

I assume the same motivation applies for behavior of the constAllZero function.

-chunk :: ByteArray b => Int -> b -> [b]
+chunk :: Int -> B.ByteString -> [B.ByteString]

The ByteArray and ByteArrayAccess classes are quite a useful way to support abstraction over different byte buffer representations. Especially considering these functions are all already generic on the ByteArray / ByteArrayAccess instance, it would be nice if there were a way to keep this. I see that crypton already has internal names for these things (Crypto.Internal.ByteArray) that would make them quite easy to redirect to another library. Perhaps this part of memory can be saved - either as part of crypton or some new library?

@kazu-yamamoto
Copy link
Owner

FYI: Vincent has completely gone out from the Haskell community.
I guess no package updates in the future.
https://twitter.com/vincenthz/status/1704395906374889934

@joeyh
Copy link

joeyh commented Jul 30, 2024

I have been bitten by the dependency on basement. It fails to build on 32 bit architectures with current versions of ghc. Fix is easy but it won't be applied to basement. haskell-foundation/foundation#565

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants