From 271772250c1a5ddd3bad93cf7f2125ad5d87cb5c Mon Sep 17 00:00:00 2001 From: Joris Dral Date: Tue, 7 Oct 2025 16:46:08 +0200 Subject: [PATCH 1/7] Update references to "C++ API reference" Previously, we were calling this documentation the "C++ Botan documentation". Calling it "C++ API reference" is closer to what the handbook calls it at https://botan.randombit.net/handbook/api_ref/contents.html --- botan-bindings/src/Botan/Bindings/PwdHash.hs | 64 ++++++++++---------- botan-low/src/Botan/Low/PwdHash.hs | 10 +-- doc/ModuleHierarchy.md | 7 +-- 3 files changed, 40 insertions(+), 41 deletions(-) diff --git a/botan-bindings/src/Botan/Bindings/PwdHash.hs b/botan-bindings/src/Botan/Bindings/PwdHash.hs index f4fe87d..5c40342 100644 --- a/botan-bindings/src/Botan/Bindings/PwdHash.hs +++ b/botan-bindings/src/Botan/Bindings/PwdHash.hs @@ -10,7 +10,7 @@ Portability : POSIX This module is based on the [Password Based Key Deriviation](https://botan.randombit.net/handbook/api_ref/pbkdf.html) section of -the C++ Botan documentation. +the C++ API reference. -} {-# LANGUAGE CApiFFI #-} @@ -41,8 +41,8 @@ import Botan.Bindings.Prelude -- There are a number of schemes available to be used as the PBKDF algorithm for -- 'botan_pwdhash' and 'botan_pwdhash_timed', which are listed in the [Available -- Schemes](https://botan.randombit.net/handbook/api_ref/pbkdf.html#available-schemes) --- section of the C++ Botan documentation. A pattern synonym for the name of --- each of the available schemes is included in these Haskell bindings. +-- section of the C++ API reference. A pattern synonym for the +-- name of each of the available schemes is included in these Haskell bindings. pattern BOTAN_PBKDF_PBKDF2 , BOTAN_PBKDF_SCRYPT @@ -59,7 +59,7 @@ pattern BOTAN_PBKDF_PBKDF2 -- 'botan_pwdhash_timed' directly. Instead, the scheme name should be -- parameterised by a hash function. For more information see the [Available -- Schemes](https://botan.randombit.net/handbook/api_ref/pbkdf.html#available-schemes) --- section of the C++ Botan documentation +-- section of the C++ API reference. pattern BOTAN_PBKDF_PBKDF2 = "PBKDF2" -- | Name of the @Scrypt@ scheme @@ -83,7 +83,7 @@ pattern BOTAN_PBKDF_BCRYPT_PBKDF = "Bcrypt-PBKDF" -- 'botan_pwdhash_timed' directly. Instead, the scheme name should be -- parameterised by a hash function. For more information see the [Available -- Schemes](https://botan.randombit.net/handbook/api_ref/pbkdf.html#available-schemes) --- section of the C++ Botan documentation +-- section of the C++ API reference. pattern BOTAN_PBKDF_OPENPGP_S2K = "OpenPGP-S2K" {------------------------------------------------------------------------------- @@ -99,19 +99,19 @@ pattern BOTAN_PBKDF_OPENPGP_S2K = "OpenPGP-S2K" -- C++ function for more information about the meaning of the parameters. -- foreign import capi safe "botan/ffi.h botan_pwdhash" - botan_pwdhash - :: ConstPtr CChar -- ^ __algo__: PBKDF algorithm, e.g., "PBKDF2(SHA-256)" or "Scrypt" - -> CSize -- ^ __param1__: the first PBKDF algorithm parameter - -> CSize -- ^ __param2__: the second PBKDF algorithm parameter (may be zero if unneeded) - -> CSize -- ^ __param3__: the third PBKDF algorithm parameter (may be zero if unneeded) - -> Ptr Word8 -- ^ __out[]__: buffer to store the derived key, must be of out_len bytes - -> CSize -- ^ __out_len__: the desired length of the key to produce - -> ConstPtr CChar -- ^ __passphrase__: the password to derive the key from - -> CSize -- ^ __passphrase_len__: if > 0, specifies length of password. If len == 0, then - -- strlen will be called on passphrase to compute the length. - -> ConstPtr Word8 -- ^ __salt[]__: a randomly chosen salt - -> CSize -- ^ __salt_len__: length of salt in bytes - -> IO CInt -- ^ 0 on success, a negative value on failure + botan_pwdhash + :: ConstPtr CChar -- ^ __algo__: PBKDF algorithm, e.g., "PBKDF2(SHA-256)" or "Scrypt" + -> CSize -- ^ __param1__: the first PBKDF algorithm parameter + -> CSize -- ^ __param2__: the second PBKDF algorithm parameter (may be zero if unneeded) + -> CSize -- ^ __param3__: the third PBKDF algorithm parameter (may be zero if unneeded) + -> Ptr Word8 -- ^ __out[]__: buffer to store the derived key, must be of out_len bytes + -> CSize -- ^ __out_len__: the desired length of the key to produce + -> ConstPtr CChar -- ^ __passphrase__: the password to derive the key from + -> CSize -- ^ __passphrase_len__: if > 0, specifies length of password. If len == 0, then + -- strlen will be called on passphrase to compute the length. + -> ConstPtr Word8 -- ^ __salt[]__: a randomly chosen salt + -> CSize -- ^ __salt_len__: length of salt in bytes + -> IO CInt -- ^ 0 on success, a negative value on failure -- | Derive a cryptographic key from a passphrase using algorithm-specific -- parameters that are tuned automatically for a desired running time of the @@ -135,17 +135,17 @@ foreign import capi safe "botan/ffi.h botan_pwdhash" -- > param3 = parallelism -- foreign import capi safe "botan/ffi.h botan_pwdhash_timed" - botan_pwdhash_timed - :: ConstPtr CChar -- ^ __algo__: PBKDF algorithm, e.g., "Scrypt" or "PBKDF2(SHA-256)" - -> Word32 -- ^ __msec__: the desired runtime in milliseconds - -> Ptr CSize -- ^ __param1__: will be set to the first PBKDF algorithm parameter - -> Ptr CSize -- ^ __param2__: will be set to the second PBKDF algorithm parameter (may be zero if unneeded) - -> Ptr CSize -- ^ __param3__: will be set to the third PBKDF algorithm parameter (may be zero if unneeded) - -> Ptr Word8 -- ^ __out[]__: buffer to store the derived key, must be of out_len bytes - -> CSize -- ^ __out_len__: the desired length of the key to produce - -> ConstPtr CChar -- ^ __passphrase__: the password to derive the key from - -> CSize -- ^ __passphrase_len__: if > 0, specifies length of password. If len == 0, then - -- strlen will be called on passphrase to compute the length. - -> ConstPtr Word8 -- ^ __salt[]__: a randomly chosen salt - -> CSize -- ^ __salt_len__: length of salt in bytes - -> IO CInt -- ^ 0 on success, a negative value on failure + botan_pwdhash_timed + :: ConstPtr CChar -- ^ __algo__: PBKDF algorithm, e.g., "Scrypt" or "PBKDF2(SHA-256)" + -> Word32 -- ^ __msec__: the desired runtime in milliseconds + -> Ptr CSize -- ^ __param1__: will be set to the first PBKDF algorithm parameter + -> Ptr CSize -- ^ __param2__: will be set to the second PBKDF algorithm parameter (may be zero if unneeded) + -> Ptr CSize -- ^ __param3__: will be set to the third PBKDF algorithm parameter (may be zero if unneeded) + -> Ptr Word8 -- ^ __out[]__: buffer to store the derived key, must be of out_len bytes + -> CSize -- ^ __out_len__: the desired length of the key to produce + -> ConstPtr CChar -- ^ __passphrase__: the password to derive the key from + -> CSize -- ^ __passphrase_len__: if > 0, specifies length of password. If len == 0, then + -- strlen will be called on passphrase to compute the length. + -> ConstPtr Word8 -- ^ __salt[]__: a randomly chosen salt + -> CSize -- ^ __salt_len__: length of salt in bytes + -> IO CInt -- ^ 0 on success, a negative value on failure diff --git a/botan-low/src/Botan/Low/PwdHash.hs b/botan-low/src/Botan/Low/PwdHash.hs index 41ab0f6..fb2ec50 100644 --- a/botan-low/src/Botan/Low/PwdHash.hs +++ b/botan-low/src/Botan/Low/PwdHash.hs @@ -10,7 +10,7 @@ Portability : POSIX This module is based on the [Password Based Key Deriviation](https://botan.randombit.net/handbook/api_ref/pbkdf.html) section of -the C++ Botan documentation. +the C++ API reference. -} module Botan.Low.PwdHash ( @@ -46,8 +46,8 @@ import Botan.Low.Prelude -- There are a number of schemes available to be used as the PBKDF algorithm for -- 'pwdhash' and 'pwdhashTimed', which are listed in the [Available -- Schemes](https://botan.randombit.net/handbook/api_ref/pbkdf.html#available-schemes) --- section of the C++ Botan documentation. A pattern synonym for the name of --- each of the available schemes is included in these Haskell bindings. +-- section of the C++ API reference. A pattern synonym for the +-- name of each of the available schemes is included in these Haskell bindings. -- | The name of a key derivation scheme used as a PBKDF algorithm type PBKDFName = ByteString @@ -67,7 +67,7 @@ pattern PBKDF2 -- 'pwdhashTimed' directly. Instead, the scheme name should be parameterised by -- a hash function using 'pbkdf2'. For more information see the [Available -- Schemes](https://botan.randombit.net/handbook/api_ref/pbkdf.html#available-schemes) --- section of the C++ Botan documentation +-- section of the C++ API reference. pattern PBKDF2 = BOTAN_PBKDF_PBKDF2 -- | Create a valid scheme name for @PBKDF2@ parameterised over a hash function @@ -100,7 +100,7 @@ pattern Bcrypt_PBKDF = BOTAN_PBKDF_BCRYPT_PBKDF -- 'pwdhashTimed' directly. Instead, the scheme name should be parameterised by -- a hash function using 'openPGP_S2K'. For more information see the [Available -- Schemes](https://botan.randombit.net/handbook/api_ref/pbkdf.html#available-schemes) --- section of the C++ Botan documentation +-- section of the C++ API reference. pattern OpenPGP_S2K = BOTAN_PBKDF_OPENPGP_S2K diff --git a/doc/ModuleHierarchy.md b/doc/ModuleHierarchy.md index 5c63402..42c1f72 100644 --- a/doc/ModuleHierarchy.md +++ b/doc/ModuleHierarchy.md @@ -34,9 +34,8 @@ suffixes. Note that the C FFI documentation is not complete, so some Haskell modules export bindings to C entities that are not described in the C FFI documentation. -Such modules are instead based on the [C++ API reference -documentation][botan:api:pwdhash]. Again, all three packages use the same module -suffixes: +Such modules are instead based on the [C++ API reference][botan:api:pwdhash]. +Again, all three packages use the same module suffixes: * [Password Based Key Derivation][botan:api:pwdhash]: `PwdHash` @@ -55,4 +54,4 @@ as well. [botan:ffi:utility-functions]: https://botan.randombit.net/handbook/api_ref/ffi.html#utility-functions [botan:api]: https://botan.randombit.net/handbook/api_ref/contents.html -[botan:api:pwdhash]: https://botan.randombit.net/handbook/api_ref/pbkdf.html#available-schemes +[botan:api:pwdhash]: https://botan.randombit.net/handbook/api_ref/pbkdf.html From 53066dc3440cc3bc9e44fbee5f8502a7f44ccdea Mon Sep 17 00:00:00 2001 From: Joris Dral Date: Tue, 7 Oct 2025 18:06:36 +0200 Subject: [PATCH 2/7] Rename `test_pwdhash_PBKDF2_badSchemeName` to `test_pwdhash_badSchemeName` --- botan-low/test/Test/Botan/Low/PwdHash.hs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/botan-low/test/Test/Botan/Low/PwdHash.hs b/botan-low/test/Test/Botan/Low/PwdHash.hs index d155e54..999a117 100644 --- a/botan-low/test/Test/Botan/Low/PwdHash.hs +++ b/botan-low/test/Test/Botan/Low/PwdHash.hs @@ -21,10 +21,10 @@ tests :: IO TestTree tests = do specs <- testSpec "spec_pwdhash" spec_pwdhash pure $ testGroup "Test.Botan.Low.PwdHash" [ - testCase "test_pwdhash_PBKDF2_badSchemeName" $ - test_pwdhash_PBKDF2_badSchemeName False - , testCase "test_pwdhashTimed_PBKDF2_badSchemeName" $ - test_pwdhash_PBKDF2_badSchemeName True + testCase "test_pwdhash_badSchemeName" $ + test_pwdhash_badSchemeName False + , testCase "test_pwdhashTimed_badSchemeName" $ + test_pwdhash_badSchemeName True , specs ] @@ -99,8 +99,8 @@ salt = "salt" -- | Test that using 'pwdhash' or 'pwdhashTimed' with bad scheme names results -- in errors. -test_pwdhash_PBKDF2_badSchemeName :: Bool -> Assertion -test_pwdhash_PBKDF2_badSchemeName useTimed = do +test_pwdhash_badSchemeName :: Bool -> Assertion +test_pwdhash_badSchemeName useTimed = do -- PBKDF2 go PBKDF2 >>= \case Left BadParameterException{} -> pure () From 5acd14d756c7713aa503454f695eb7681c3d66a9 Mon Sep 17 00:00:00 2001 From: Joris Dral Date: Tue, 7 Oct 2025 17:37:04 +0200 Subject: [PATCH 3/7] `SRP6`: enable `botan-low-srp6-tests` again The test-suite was disabled because it fails to run successfully. The test-suite is enabled again, even though the tests still fail. Fixes for these tests will be included in coming commits. --- botan-low/botan-low.cabal | 4 ---- botan-low/test/Botan/Low/SRP6Spec.hs | 11 ----------- 2 files changed, 15 deletions(-) diff --git a/botan-low/botan-low.cabal b/botan-low/botan-low.cabal index 7220e70..2e5f780 100644 --- a/botan-low/botan-low.cabal +++ b/botan-low/botan-low.cabal @@ -746,15 +746,11 @@ test-suite botan-low-rng-tests test-suite botan-low-srp6-tests import: warnings, language - - -- TODO: temporarily disabled because the test suite fails. See issue #33. - buildable: False type: exitcode-stdio-1.0 main-is: Botan/Low/SRP6Spec.hs hs-source-dirs: test/ build-depends: , base - , botan-bindings , botan-low , bytestring , hspec diff --git a/botan-low/test/Botan/Low/SRP6Spec.hs b/botan-low/test/Botan/Low/SRP6Spec.hs index 481efd9..8403b60 100644 --- a/botan-low/test/Botan/Low/SRP6Spec.hs +++ b/botan-low/test/Botan/Low/SRP6Spec.hs @@ -1,11 +1,3 @@ -{-# LANGUAGE CPP #-} - -#if defined(MIN_VERSION_GLASGOW_HASKELL) -#if MIN_VERSION_GLASGOW_HASKELL(9,8,0,0) -{-# OPTIONS_GHC -Wwarn=x-partial #-} -#endif -#endif - module Main (main) where import Test.Prelude @@ -51,9 +43,6 @@ groupIds = , "dsa/botan/3072" ] -groupId :: DLGroupName -groupId = head groupIds - -- TODO: Test which hashes work hashId :: HashName hashId = "SHA-256" From 2dd5921d9ab2b69f0eb220d3b2f90cb5e82b72ac Mon Sep 17 00:00:00 2001 From: Joris Dral Date: Wed, 8 Oct 2025 09:28:35 +0200 Subject: [PATCH 4/7] `SRP6`: reduce levels of indentation, improve syntax highlighting Reduced levels of indentation means we have more horizontal real estate to work with in Haskell files. Putting `::` on the same line as function definitions improves syntax highlighting. --- botan-bindings/src/Botan/Bindings/SRP6.hs | 106 +++++----- botan-low/src/Botan/Low/SRP6.hs | 247 +++++++++++----------- 2 files changed, 176 insertions(+), 177 deletions(-) diff --git a/botan-bindings/src/Botan/Bindings/SRP6.hs b/botan-bindings/src/Botan/Bindings/SRP6.hs index 9901d18..7bab4c1 100644 --- a/botan-bindings/src/Botan/Bindings/SRP6.hs +++ b/botan-bindings/src/Botan/Bindings/SRP6.hs @@ -52,77 +52,77 @@ data {-# CTYPE "botan/ffi.h" "struct botan_srp6_server_session_struct" #-} Botan -- | Botan SRP-6 server session object newtype {-# CTYPE "botan/ffi.h" "botan_srp6_server_session_t" #-} BotanSRP6ServerSession - = MkBotanSRP6ServerSession { runBotanSRP6ServerSession :: Ptr BotanSRP6ServerSessionStruct } - deriving newtype (Eq, Ord, Storable) + = MkBotanSRP6ServerSession { runBotanSRP6ServerSession :: Ptr BotanSRP6ServerSessionStruct } + deriving newtype (Eq, Ord, Storable) -- | Frees all resources of the SRP-6 server session object foreign import capi safe "botan/ffi.h &botan_srp6_server_session_destroy" - botan_srp6_server_session_destroy - :: FinalizerPtr BotanSRP6ServerSessionStruct + botan_srp6_server_session_destroy + :: FinalizerPtr BotanSRP6ServerSessionStruct -- | Initialize an SRP-6 server session object foreign import capi safe "botan/ffi.h botan_srp6_server_session_init" - botan_srp6_server_session_init - :: Ptr BotanSRP6ServerSession -- ^ __srp6__: SRP-6 server session object - -> IO CInt + botan_srp6_server_session_init + :: Ptr BotanSRP6ServerSession -- ^ __srp6__: SRP-6 server session object + -> IO CInt -- | SRP-6 Server side step 1: Generate a server B-value foreign import capi safe "botan/ffi.h botan_srp6_server_session_step1" - botan_srp6_server_session_step1 - :: BotanSRP6ServerSession -- ^ __srp6__: SRP-6 server session object - -> ConstPtr Word8 -- ^ __verifier[]__: the verification value saved from client registration - -> CSize -- ^ __verifier_len__: SRP-6 verifier value length - -> ConstPtr CChar -- ^ __group_id__: the SRP group id - -> ConstPtr CChar -- ^ __hash_id__: the SRP hash in use - -> BotanRNG -- ^ __rng_obj__: a random number generator object - -> Ptr Word8 -- ^ __B_pub[]__: out buffer to store the SRP-6 B value - -> Ptr CSize -- ^ __B_pub_len__: SRP-6 B value length - -> IO CInt -- ^ 0 on success, negative on failure + botan_srp6_server_session_step1 + :: BotanSRP6ServerSession -- ^ __srp6__: SRP-6 server session object + -> ConstPtr Word8 -- ^ __verifier[]__: the verification value saved from client registration + -> CSize -- ^ __verifier_len__: SRP-6 verifier value length + -> ConstPtr CChar -- ^ __group_id__: the SRP group id + -> ConstPtr CChar -- ^ __hash_id__: the SRP hash in use + -> BotanRNG -- ^ __rng_obj__: a random number generator object + -> Ptr Word8 -- ^ __B_pub[]__: out buffer to store the SRP-6 B value + -> Ptr CSize -- ^ __B_pub_len__: SRP-6 B value length + -> IO CInt -- ^ 0 on success, negative on failure -- | SRP-6 Server side step 2: Generate the server shared key foreign import capi safe "botan/ffi.h botan_srp6_server_session_step2" - botan_srp6_server_session_step2 - :: BotanSRP6ServerSession -- ^ __srp6__: SRP-6 server session object - -> ConstPtr Word8 -- ^ __A[]__: the client's value - -> CSize -- ^ __A_len__: the client's value length - -> Ptr Word8 -- ^ __key[]__: out buffer to store the symmetric key value - -> Ptr CSize -- ^ __key_len__: symmetric key length - -> IO CInt -- ^ 0 on success, negative on failure + botan_srp6_server_session_step2 + :: BotanSRP6ServerSession -- ^ __srp6__: SRP-6 server session object + -> ConstPtr Word8 -- ^ __A[]__: the client's value + -> CSize -- ^ __A_len__: the client's value length + -> Ptr Word8 -- ^ __key[]__: out buffer to store the symmetric key value + -> Ptr CSize -- ^ __key_len__: symmetric key length + -> IO CInt -- ^ 0 on success, negative on failure -- | SRP-6 Client side step 1: Generate a new SRP-6 verifier foreign import capi safe "botan/ffi.h botan_srp6_generate_verifier" - botan_srp6_generate_verifier - :: ConstPtr CChar -- ^ __identifier__: a username or other client identifier - -> ConstPtr CChar -- ^ __password__: the secret used to authenticate user - -> ConstPtr Word8 -- ^ __salt[]__: a randomly chosen value, at least 128 bits long - -> CSize -- ^ __salt_len__: the length of salt - -> ConstPtr CChar -- ^ __group_id__: specifies the shared SRP group - -> ConstPtr CChar -- ^ __hash_id__: specifies a secure hash function - -> Ptr Word8 -- ^ __verifier[]__: out buffer to store the SRP-6 verifier value - -> Ptr CSize -- ^ __verifier_len__: SRP-6 verifier value length - -> IO CInt -- ^ 0 on success, negative on failure + botan_srp6_generate_verifier + :: ConstPtr CChar -- ^ __identifier__: a username or other client identifier + -> ConstPtr CChar -- ^ __password__: the secret used to authenticate user + -> ConstPtr Word8 -- ^ __salt[]__: a randomly chosen value, at least 128 bits long + -> CSize -- ^ __salt_len__: the length of salt + -> ConstPtr CChar -- ^ __group_id__: specifies the shared SRP group + -> ConstPtr CChar -- ^ __hash_id__: specifies a secure hash function + -> Ptr Word8 -- ^ __verifier[]__: out buffer to store the SRP-6 verifier value + -> Ptr CSize -- ^ __verifier_len__: SRP-6 verifier value length + -> IO CInt -- ^ 0 on success, negative on failure -- | SRP6a Client side step 2: Generate a client A-value and the client shared key foreign import capi safe "botan/ffi.h botan_srp6_client_agree" - botan_srp6_client_agree - :: ConstPtr CChar -- ^ __username__: the username we are attempting login for - -> ConstPtr CChar -- ^ __password__: the password we are attempting to use - -> ConstPtr CChar -- ^ __group_id__: specifies the shared SRP group - -> ConstPtr CChar -- ^ __hash_id__: specifies a secure hash function - -> ConstPtr Word8 -- ^ __salt[]__: is the salt value sent by the server - -> CSize -- ^ __salt_len__: the length of salt - -> ConstPtr Word8 -- ^ __uint8_t__: B[] is the server's public value - -> CSize -- ^ __B_len__: is the server's public value length - -> BotanRNG -- ^ __rng_obj__: is a random number generator object - -> Ptr Word8 -- ^ __A[]__: out buffer to store the SRP-6 A value - -> Ptr CSize -- ^ __A_len__: SRP-6 A verifier value length - -> Ptr Word8 -- ^ __K[]__: out buffer to store the symmetric value - -> Ptr CSize -- ^ __K_len__: symmetric key length - -> IO CInt -- ^ 0 on success, negative on failure + botan_srp6_client_agree + :: ConstPtr CChar -- ^ __username__: the username we are attempting login for + -> ConstPtr CChar -- ^ __password__: the password we are attempting to use + -> ConstPtr CChar -- ^ __group_id__: specifies the shared SRP group + -> ConstPtr CChar -- ^ __hash_id__: specifies a secure hash function + -> ConstPtr Word8 -- ^ __salt[]__: is the salt value sent by the server + -> CSize -- ^ __salt_len__: the length of salt + -> ConstPtr Word8 -- ^ __uint8_t__: B[] is the server's public value + -> CSize -- ^ __B_len__: is the server's public value length + -> BotanRNG -- ^ __rng_obj__: is a random number generator object + -> Ptr Word8 -- ^ __A[]__: out buffer to store the SRP-6 A value + -> Ptr CSize -- ^ __A_len__: SRP-6 A verifier value length + -> Ptr Word8 -- ^ __K[]__: out buffer to store the symmetric value + -> Ptr CSize -- ^ __K_len__: symmetric key length + -> IO CInt -- ^ 0 on success, negative on failure -- | Return the size, in bytes, of the prime associated with group_id foreign import capi safe "botan/ffi.h botan_srp6_group_size" - botan_srp6_group_size - :: ConstPtr CChar -- ^ __group_id__ - -> Ptr CSize -- ^ __group_p_bytes__ - -> IO CInt + botan_srp6_group_size + :: ConstPtr CChar -- ^ __group_id__ + -> Ptr CSize -- ^ __group_p_bytes__ + -> IO CInt diff --git a/botan-low/src/Botan/Low/SRP6.hs b/botan-low/src/Botan/Low/SRP6.hs index f1a06e0..a3e6fcb 100644 --- a/botan-low/src/Botan/Low/SRP6.hs +++ b/botan-low/src/Botan/Low/SRP6.hs @@ -14,13 +14,10 @@ authenticated key exchange protocol. -} module Botan.Low.SRP6 ( - -- * Secure Random Password 6a -- $introduction - -- * Usage -- $usage - SRP6ServerSession(..) , withSRP6ServerSession , srp6ServerSessionInit @@ -30,16 +27,12 @@ module Botan.Low.SRP6 ( , srp6GenerateVerifier , srp6ClientAgree , srp6GroupSize - -- * SRP6 Types - , SRP6Verifier , SRP6BValue , SRP6AValue , SRP6SharedSecret - -- * SRP discrete logarithm groups - , pattern MODP_SRP_1024 , pattern MODP_SRP_1536 , pattern MODP_SRP_2048 @@ -47,7 +40,6 @@ module Botan.Low.SRP6 ( , pattern MODP_SRP_4096 , pattern MODP_SRP_6144 , pattern MODP_SRP_8192 - ) where import Botan.Bindings.SRP6 @@ -193,134 +185,141 @@ createSRP6ServerSession :: (Ptr BotanSRP6ServerSession -> IO CInt) -> IO SRP6S botan_srp6_server_session_destroy -- | Initialize an SRP-6 server session object -srp6ServerSessionInit - :: IO SRP6ServerSession -- ^ __srp6__: SRP-6 server session object +srp6ServerSessionInit :: + IO SRP6ServerSession -- ^ __srp6__: SRP-6 server session object srp6ServerSessionInit = createSRP6ServerSession botan_srp6_server_session_init -- | SRP-6 Server side step 1: Generate a server B-value -srp6ServerSessionStep1 - :: SRP6ServerSession -- ^ __srp6__: SRP-6 server session object - -> SRP6Verifier -- ^ __verifier[]__: the verification value saved from client registration - -> DLGroupName -- ^ __group_id__: the SRP group id - -> HashName -- ^ __hash_id__: the SRP hash in use - -> RNG -- ^ __rng_obj__: a random number generator object - -> IO SRP6BValue -- ^ __B_pub[]__: out buffer to store the SRP-6 B value -srp6ServerSessionStep1 srp6 verifier groupId hashId rng = withSRP6ServerSession srp6 $ \ srp6Ptr -> do - asBytesLen verifier $ \ verifierPtr verifierLen -> do - asCString groupId $ \ groupIdPtr -> do - asCString hashId $ \ hashIdPtr -> do - withRNG rng $ \ botanRNG -> do - allocBytesQuerying $ \ outPtr outLen -> botan_srp6_server_session_step1 - srp6Ptr - (ConstPtr verifierPtr) - verifierLen - (ConstPtr groupIdPtr) - (ConstPtr hashIdPtr) - botanRNG - outPtr - outLen +srp6ServerSessionStep1 :: + SRP6ServerSession -- ^ __srp6__: SRP-6 server session object + -> SRP6Verifier -- ^ __verifier[]__: the verification value saved from client registration + -> DLGroupName -- ^ __group_id__: the SRP group id + -> HashName -- ^ __hash_id__: the SRP hash in use + -> RNG -- ^ __rng_obj__: a random number generator object + -> IO SRP6BValue -- ^ __B_pub[]__: out buffer to store the SRP-6 B value +srp6ServerSessionStep1 srp6 verifier groupId hashId rng = + withSRP6ServerSession srp6 $ \ srp6Ptr -> + asBytesLen verifier $ \ verifierPtr verifierLen -> + asCString groupId $ \ groupIdPtr -> + asCString hashId $ \ hashIdPtr -> + withRNG rng $ \ botanRNG -> + allocBytesQuerying $ \ outPtr outLen -> + botan_srp6_server_session_step1 + srp6Ptr + (ConstPtr verifierPtr) + verifierLen + (ConstPtr groupIdPtr) + (ConstPtr hashIdPtr) + botanRNG + outPtr + outLen -- | SRP-6 Server side step 2: Generate the server shared key -srp6ServerSessionStep2 - :: SRP6ServerSession -- ^ __srp6__: SRP-6 server session object - -> SRP6AValue -- ^ __A[]__: the client's value - -> IO SRP6SharedSecret -- ^ __key[]__: out buffer to store the symmetric key value -srp6ServerSessionStep2 srp6 a = withSRP6ServerSession srp6 $ \ srp6Ptr -> do - asBytesLen a $ \ aPtr aLen -> do - allocBytesQuerying $ \ outPtr outLen -> botan_srp6_server_session_step2 - srp6Ptr - (ConstPtr aPtr) - aLen - outPtr - outLen +srp6ServerSessionStep2 :: + SRP6ServerSession -- ^ __srp6__: SRP-6 server session object + -> SRP6AValue -- ^ __A[]__: the client's value + -> IO SRP6SharedSecret -- ^ __key[]__: out buffer to store the symmetric key value +srp6ServerSessionStep2 srp6 a = + withSRP6ServerSession srp6 $ \ srp6Ptr -> + asBytesLen a $ \ aPtr aLen -> + allocBytesQuerying $ \ outPtr outLen -> + botan_srp6_server_session_step2 + srp6Ptr + (ConstPtr aPtr) + aLen + outPtr + outLen -- | SRP-6 Client side step 1: Generate a new SRP-6 verifier -srp6GenerateVerifier - :: Identifier -- ^ __identifier__: a username or other client identifier - -> Password -- ^ __password__: the secret used to authenticate user - -> Salt -- ^ __salt[]__: a randomly chosen value, at least 128 bits long - -> DLGroupName -- ^ __group_id__: specifies the shared SRP group - -> HashName -- ^ __hash_id__: specifies a secure hash function - -> IO SRP6Verifier -- ^ __verifier[]__: out buffer to store the SRP-6 verifier value -srp6GenerateVerifier identifier password salt groupId hashId = asCString identifier $ \ identifierPtr -> do - asCString password $ \ passwordPtr -> do - asBytesLen salt $ \ saltPtr saltLen -> do - asCString groupId $ \ groupIdPtr -> do - asCString hashId $ \ hashIdPtr -> do - allocBytesQuerying $ \ outPtr outLen -> botan_srp6_generate_verifier - (ConstPtr identifierPtr) - (ConstPtr passwordPtr) - (ConstPtr saltPtr) - saltLen - (ConstPtr groupIdPtr) - (ConstPtr hashIdPtr) - outPtr - outLen +srp6GenerateVerifier :: + Identifier -- ^ __identifier__: a username or other client identifier + -> Password -- ^ __password__: the secret used to authenticate user + -> Salt -- ^ __salt[]__: a randomly chosen value, at least 128 bits long + -> DLGroupName -- ^ __group_id__: specifies the shared SRP group + -> HashName -- ^ __hash_id__: specifies a secure hash function + -> IO SRP6Verifier -- ^ __verifier[]__: out buffer to store the SRP-6 verifier value +srp6GenerateVerifier identifier password salt groupId hashId = + asCString identifier $ \ identifierPtr -> + asCString password $ \ passwordPtr -> + asBytesLen salt $ \ saltPtr saltLen -> + asCString groupId $ \ groupIdPtr -> + asCString hashId $ \ hashIdPtr -> + allocBytesQuerying $ \ outPtr outLen -> + botan_srp6_generate_verifier + (ConstPtr identifierPtr) + (ConstPtr passwordPtr) + (ConstPtr saltPtr) + saltLen + (ConstPtr groupIdPtr) + (ConstPtr hashIdPtr) + outPtr + outLen -- NOTE: ORDER IS DIFFERENT FROM SERVER GENERATE VERIFIER -- | SRP6a Client side step 2: Generate a client A-value and the client shared key -srp6ClientAgree - :: Identifier -- ^ __username__: the username we are attempting login for - -> Password -- ^ __password__: the password we are attempting to use - -> DLGroupName -- ^ __group_id__: specifies the shared SRP group - -> HashName -- ^ __hash_id__: specifies a secure hash function - -> Salt -- ^ __salt[]__: is the salt value sent by the server - -> SRP6BValue -- ^ __uint8_t__: B[] is the server's public value - -> RNG -- ^ __rng_obj__: is a random number generator object - -> IO (SRP6AValue, SRP6SharedSecret) -- @(A,K)@ -srp6ClientAgree identifier password groupId hashId salt b rng = do - asCString identifier $ \ identifierPtr -> do - asCString password $ \ passwordPtr -> do - asCString groupId $ \ groupIdPtr -> do - asCString hashId $ \ hashIdPtr -> do - asBytesLen salt $ \ saltPtr saltLen -> do - asBytesLen b $ \ bPtr bLen -> do - withRNG rng $ \ botanRNG -> do - alloca $ \ aSzPtr -> do - alloca $ \ kSzPtr -> do - -- Query sizes - -- TODO: Actually ensure expected error (insufficient buffer space) - -- and propagate unexpected errors - _ <- botan_srp6_client_agree - (ConstPtr identifierPtr) - (ConstPtr passwordPtr) - (ConstPtr groupIdPtr) - (ConstPtr hashIdPtr) - (ConstPtr saltPtr) - saltLen - (ConstPtr bPtr) - bLen - botanRNG - nullPtr - aSzPtr - nullPtr - kSzPtr - kSz <- fromIntegral <$> peek kSzPtr - aSz <- fromIntegral <$> peek aSzPtr - allocBytesWith kSz $ \ kPtr -> do - allocBytes aSz $ \ aPtr -> do - throwBotanIfNegative_ $ botan_srp6_client_agree - (ConstPtr identifierPtr) - (ConstPtr passwordPtr) - (ConstPtr groupIdPtr) - (ConstPtr hashIdPtr) - (ConstPtr saltPtr) - saltLen - (ConstPtr bPtr) - bLen - botanRNG - aPtr - aSzPtr - kPtr - kSzPtr +srp6ClientAgree :: + Identifier -- ^ __username__: the username we are attempting login for + -> Password -- ^ __password__: the password we are attempting to use + -> DLGroupName -- ^ __group_id__: specifies the shared SRP group + -> HashName -- ^ __hash_id__: specifies a secure hash function + -> Salt -- ^ __salt[]__: is the salt value sent by the server + -> SRP6BValue -- ^ __uint8_t__: B[] is the server's public value + -> RNG -- ^ __rng_obj__: is a random number generator object + -> IO (SRP6AValue, SRP6SharedSecret) -- @(A,K)@ +srp6ClientAgree identifier password groupId hashId salt b rng = + asCString identifier $ \ identifierPtr -> + asCString password $ \ passwordPtr -> + asCString groupId $ \ groupIdPtr -> + asCString hashId $ \ hashIdPtr -> + asBytesLen salt $ \ saltPtr saltLen -> + asBytesLen b $ \ bPtr bLen -> + withRNG rng $ \ botanRNG -> + alloca $ \ aSzPtr -> + alloca $ \ kSzPtr -> do + -- Query sizes + -- TODO: Actually ensure expected error (insufficient buffer space) + -- and propagate unexpected errors + _ <- botan_srp6_client_agree + (ConstPtr identifierPtr) + (ConstPtr passwordPtr) + (ConstPtr groupIdPtr) + (ConstPtr hashIdPtr) + (ConstPtr saltPtr) + saltLen + (ConstPtr bPtr) + bLen + botanRNG + nullPtr + aSzPtr + nullPtr + kSzPtr + kSz <- fromIntegral <$> peek kSzPtr + aSz <- fromIntegral <$> peek aSzPtr + allocBytesWith kSz $ \ kPtr -> + allocBytes aSz $ \ aPtr -> + throwBotanIfNegative_ $ botan_srp6_client_agree + (ConstPtr identifierPtr) + (ConstPtr passwordPtr) + (ConstPtr groupIdPtr) + (ConstPtr hashIdPtr) + (ConstPtr saltPtr) + saltLen + (ConstPtr bPtr) + bLen + botanRNG + aPtr + aSzPtr + kPtr + kSzPtr -- NOTE: Missing FFI function: srp6_group_identifierz -- | Return the size, in bytes, of the prime associated with group_id -srp6GroupSize - :: DLGroupName -- ^ __group_id__ - -> IO Int -- ^ __group_p_bytes__ -srp6GroupSize groupId = asCString groupId $ \ groupIdPtr -> do +srp6GroupSize :: + DLGroupName -- ^ __group_id__ + -> IO Int -- ^ __group_p_bytes__ +srp6GroupSize groupId = + asCString groupId $ \ groupIdPtr -> alloca $ \ szPtr -> do - throwBotanIfNegative_ $ botan_srp6_group_size (ConstPtr groupIdPtr) szPtr - fromIntegral <$> peek szPtr + throwBotanIfNegative_ $ botan_srp6_group_size (ConstPtr groupIdPtr) szPtr + fromIntegral <$> peek szPtr From 2740ebbc52dc15f3b1bf5ba6ad3f374c67149a27 Mon Sep 17 00:00:00 2001 From: Joris Dral Date: Tue, 7 Oct 2025 17:38:33 +0200 Subject: [PATCH 5/7] `SRP6`: review, refactor, fix issue #28 This includes only the relevant modules from `botan-bindings` and `botan-low`. In particular, we work around a bug in `srp6ServerSessionStep1` in `botan-low` where an exception is thrown from C code by `botan_srp6_server_session_step1` because it is called twice by `srp6ServerSessionStep1`. This is bug in the Botan C++ library, see https://github.com/randombit/botan/issues/5112. The best we can do for now is to try to not trigger the exception. The work-around we introduce is to make sure we call the erroring function (`botan_srp6_server_session_step1`) only once. Previously, we were calling it once to "query" the size of output buffers, and then again with the correct sizes of output buffers. We can instead use `botan_srp6_group_size`/`srp6GroupSize` to determine the correct sizes of output buffers immediately. This is now done this way automatically where appropriate in `botan-low`, but not in `botan-bindings`. Moreover, users can still call `botan_srp6_server_session_step1`/`srp6ServerSessionStep1` twice if they want to, meaning that the exception can still be triggered. To warn against this, we make sure to include appropriate warnings in the Haddock documentation. The other smaller changes do not affect the functionality of the code, only documentation and such. --- botan-bindings/src/Botan/Bindings/SRP6.hs | 50 ++-- botan-low/src/Botan/Low/SRP6.hs | 272 ++++++++++++---------- botan-low/test/Botan/Low/SRP6Spec.hs | 4 +- botan/src/Botan/SRP6.hs | 5 +- doc/ModuleHierarchy.md | 4 +- 5 files changed, 185 insertions(+), 150 deletions(-) diff --git a/botan-bindings/src/Botan/Bindings/SRP6.hs b/botan-bindings/src/Botan/Bindings/SRP6.hs index 7bab4c1..2e03d15 100644 --- a/botan-bindings/src/Botan/Bindings/SRP6.hs +++ b/botan-bindings/src/Botan/Bindings/SRP6.hs @@ -8,26 +8,9 @@ Maintainer : joris@well-typed.com, leo@apotheca.io Stability : experimental Portability : POSIX -The library contains an implementation of the SRP6-a password -authenticated key exchange protocol. - -A SRP client provides what is called a SRP verifier to the server. -This verifier is based on a password, but the password cannot be -easily derived from the verifier (however brute force attacks are -possible). Later, the client and server can perform an SRP exchange, -which results in a shared secret key. This key can be used for -mutual authentication and/or encryption. - -SRP works in a discrete logarithm group. Special parameter sets for -SRP6 are defined, denoted in the library as “modp/srp/”, for -example “modp/srp/2048”. - -Warning - -While knowledge of the verifier does not easily allow an attacker to -get the raw password, they could still use the verifier to impersonate -the server to the client, so verifiers should be protected as carefully -as a plaintext password would be. +This module is based on the [Secure Remote +Password](https://botan.randombit.net/handbook/api_ref/srp.html) section of the +C++ API reference. -} {-# LANGUAGE CApiFFI #-} @@ -50,12 +33,15 @@ import Botan.Bindings.RNG -- | Opaque SRP-6 server session struct data {-# CTYPE "botan/ffi.h" "struct botan_srp6_server_session_struct" #-} BotanSRP6ServerSessionStruct --- | Botan SRP-6 server session object +-- | SRP-6 server session object newtype {-# CTYPE "botan/ffi.h" "botan_srp6_server_session_t" #-} BotanSRP6ServerSession = MkBotanSRP6ServerSession { runBotanSRP6ServerSession :: Ptr BotanSRP6ServerSessionStruct } deriving newtype (Eq, Ord, Storable) -- | Frees all resources of the SRP-6 server session object +-- +-- NOTE: this a binding to the /address/ of the +-- @botan_srp6_server_session_destroy@ C function. foreign import capi safe "botan/ffi.h &botan_srp6_server_session_destroy" botan_srp6_server_session_destroy :: FinalizerPtr BotanSRP6ServerSessionStruct @@ -66,7 +52,14 @@ foreign import capi safe "botan/ffi.h botan_srp6_server_session_init" :: Ptr BotanSRP6ServerSession -- ^ __srp6__: SRP-6 server session object -> IO CInt --- | SRP-6 Server side step 1: Generate a server B-value +-- | SRP-6 Server side step 1 +-- +-- NOTE: this function should be not be invoked twice on the same server +-- session. Regardless of the result of the first invocation, the second +-- invocation will result in an error. See +-- https://github.com/randombit/botan/issues/5112 for more information. If a +-- second invocation can not be prevented, try it on a newly initialised server +-- session instead. foreign import capi safe "botan/ffi.h botan_srp6_server_session_step1" botan_srp6_server_session_step1 :: BotanSRP6ServerSession -- ^ __srp6__: SRP-6 server session object @@ -79,7 +72,7 @@ foreign import capi safe "botan/ffi.h botan_srp6_server_session_step1" -> Ptr CSize -- ^ __B_pub_len__: SRP-6 B value length -> IO CInt -- ^ 0 on success, negative on failure --- | SRP-6 Server side step 2: Generate the server shared key +-- | SRP-6 Server side step 2 foreign import capi safe "botan/ffi.h botan_srp6_server_session_step2" botan_srp6_server_session_step2 :: BotanSRP6ServerSession -- ^ __srp6__: SRP-6 server session object @@ -89,7 +82,7 @@ foreign import capi safe "botan/ffi.h botan_srp6_server_session_step2" -> Ptr CSize -- ^ __key_len__: symmetric key length -> IO CInt -- ^ 0 on success, negative on failure --- | SRP-6 Client side step 1: Generate a new SRP-6 verifier +-- | Generate a new SRP-6 verifier foreign import capi safe "botan/ffi.h botan_srp6_generate_verifier" botan_srp6_generate_verifier :: ConstPtr CChar -- ^ __identifier__: a username or other client identifier @@ -102,7 +95,7 @@ foreign import capi safe "botan/ffi.h botan_srp6_generate_verifier" -> Ptr CSize -- ^ __verifier_len__: SRP-6 verifier value length -> IO CInt -- ^ 0 on success, negative on failure --- | SRP6a Client side step 2: Generate a client A-value and the client shared key +-- | SRP6a Client side foreign import capi safe "botan/ffi.h botan_srp6_client_agree" botan_srp6_client_agree :: ConstPtr CChar -- ^ __username__: the username we are attempting login for @@ -111,7 +104,7 @@ foreign import capi safe "botan/ffi.h botan_srp6_client_agree" -> ConstPtr CChar -- ^ __hash_id__: specifies a secure hash function -> ConstPtr Word8 -- ^ __salt[]__: is the salt value sent by the server -> CSize -- ^ __salt_len__: the length of salt - -> ConstPtr Word8 -- ^ __uint8_t__: B[] is the server's public value + -> ConstPtr Word8 -- ^ __B[]__: is the server's public value -> CSize -- ^ __B_len__: is the server's public value length -> BotanRNG -- ^ __rng_obj__: is a random number generator object -> Ptr Word8 -- ^ __A[]__: out buffer to store the SRP-6 A value @@ -121,6 +114,11 @@ foreign import capi safe "botan/ffi.h botan_srp6_client_agree" -> IO CInt -- ^ 0 on success, negative on failure -- | Return the size, in bytes, of the prime associated with group_id +-- +-- This function can be used to determine the size of output buffers for +-- generated keys in the SRP6 algorithm. Such buffers need to be allocated +-- before calling SRP6 functions. An example of such a buffer is the +-- @verifier[]@ buffer in the 'botan_srp6_generate_verifier' function. foreign import capi safe "botan/ffi.h botan_srp6_group_size" botan_srp6_group_size :: ConstPtr CChar -- ^ __group_id__ diff --git a/botan-low/src/Botan/Low/SRP6.hs b/botan-low/src/Botan/Low/SRP6.hs index a3e6fcb..9be25d6 100644 --- a/botan-low/src/Botan/Low/SRP6.hs +++ b/botan-low/src/Botan/Low/SRP6.hs @@ -8,16 +8,15 @@ Maintainer : joris@well-typed.com, leo@apotheca.io Stability : experimental Portability : POSIX -The library contains an implementation of the SRP6-a password -authenticated key exchange protocol. - +This module is based on the [Secure Remote +Password](https://botan.randombit.net/handbook/api_ref/srp.html) section of the +C++ API reference. -} module Botan.Low.SRP6 ( - -- * Secure Random Password 6a - -- $introduction - -- * Usage - -- $usage + -- * Example usage + -- $usage + -- * Algorithm SRP6ServerSession(..) , withSRP6ServerSession , srp6ServerSessionInit @@ -27,12 +26,12 @@ module Botan.Low.SRP6 ( , srp6GenerateVerifier , srp6ClientAgree , srp6GroupSize - -- * SRP6 Types + -- * SRP6 Types , SRP6Verifier , SRP6BValue , SRP6AValue , SRP6SharedSecret - -- * SRP discrete logarithm groups + -- * SRP discrete logarithm groups , pattern MODP_SRP_1024 , pattern MODP_SRP_1536 , pattern MODP_SRP_2048 @@ -42,40 +41,17 @@ module Botan.Low.SRP6 ( , pattern MODP_SRP_8192 ) where +import qualified Data.ByteString.Internal as BS + import Botan.Bindings.SRP6 import Botan.Low.Error import Botan.Low.Hash -import Botan.Low.Make import Botan.Low.Prelude import Botan.Low.PubKey import Botan.Low.Remake import Botan.Low.RNG -{- $introduction - -A SRP client provides what is called a SRP verifier to the server. -This verifier is based on a password, but the password cannot be -easily derived from the verifier (however brute force attacks are -possible). Later, the client and server can perform an SRP exchange, -which results in a shared secret key. This key can be used for -mutual authentication and/or encryption. - -SRP works in a discrete logarithm group. Special parameter sets for -SRP6 are defined, denoted in the library as @modp\/srp\/@, for -example @modp\/srp\/2048@. - -Warning - -While knowledge of the verifier does not easily allow an attacker to -get the raw password, they could still use the verifier to impersonate -the server to the client, so verifiers should be protected as carefully -as a plaintext password would be. - -SRP6 may be used as part of SSL/TLS: https://www.rfc-editor.org/rfc/rfc5054 - --} - {- $usage On signup, the client generates a salt and verifier, and securely sends them to a server: @@ -175,7 +151,7 @@ type SRP6SharedSecret = ByteString newtype SRP6ServerSession = MkSRP6ServerSession { getSRP6ServerSessionForeignPtr :: ForeignPtr BotanSRP6ServerSessionStruct } withSRP6ServerSession :: SRP6ServerSession -> (BotanSRP6ServerSession -> IO a) -> IO a --- | Destroy a SRP6 server session object immediately +-- | Frees all resources of the SRP-6 server session object srp6ServerSessionDestroy :: SRP6ServerSession -> IO () createSRP6ServerSession :: (Ptr BotanSRP6ServerSession -> IO CInt) -> IO SRP6ServerSession (_, withSRP6ServerSession, srp6ServerSessionDestroy, createSRP6ServerSession, _) @@ -189,7 +165,14 @@ srp6ServerSessionInit :: IO SRP6ServerSession -- ^ __srp6__: SRP-6 server session object srp6ServerSessionInit = createSRP6ServerSession botan_srp6_server_session_init --- | SRP-6 Server side step 1: Generate a server B-value +-- | SRP-6 Server side step 1 +-- +-- NOTE: this function should be not be invoked twice on the same server +-- session. Regardless of the result of the first invocation, the second +-- invocation will result in an error. See +-- https://github.com/randombit/botan/issues/5112 for more information. If a +-- second invocation can not be prevented, try it on a newly initialised server +-- session instead. srp6ServerSessionStep1 :: SRP6ServerSession -- ^ __srp6__: SRP-6 server session object -> SRP6Verifier -- ^ __verifier[]__: the verification value saved from client registration @@ -203,34 +186,37 @@ srp6ServerSessionStep1 srp6 verifier groupId hashId rng = asCString groupId $ \ groupIdPtr -> asCString hashId $ \ hashIdPtr -> withRNG rng $ \ botanRNG -> - allocBytesQuerying $ \ outPtr outLen -> - botan_srp6_server_session_step1 - srp6Ptr - (ConstPtr verifierPtr) - verifierLen - (ConstPtr groupIdPtr) - (ConstPtr hashIdPtr) - botanRNG - outPtr - outLen - --- | SRP-6 Server side step 2: Generate the server shared key + createWithGroupSize groupId $ \ outPtr outLen -> + throwBotanIfNegative_ $ + botan_srp6_server_session_step1 + srp6Ptr + (ConstPtr verifierPtr) + verifierLen + (ConstPtr groupIdPtr) + (ConstPtr hashIdPtr) + botanRNG + outPtr + outLen + +-- | SRP-6 Server side step 2 srp6ServerSessionStep2 :: SRP6ServerSession -- ^ __srp6__: SRP-6 server session object -> SRP6AValue -- ^ __A[]__: the client's value + -> DLGroupName -- ^ the SRP group id -> IO SRP6SharedSecret -- ^ __key[]__: out buffer to store the symmetric key value -srp6ServerSessionStep2 srp6 a = +srp6ServerSessionStep2 srp6 groupId a = withSRP6ServerSession srp6 $ \ srp6Ptr -> asBytesLen a $ \ aPtr aLen -> - allocBytesQuerying $ \ outPtr outLen -> - botan_srp6_server_session_step2 - srp6Ptr - (ConstPtr aPtr) - aLen - outPtr - outLen - --- | SRP-6 Client side step 1: Generate a new SRP-6 verifier + createWithGroupSize groupId $ \ outPtr outLen -> + throwBotanIfNegative_ $ + botan_srp6_server_session_step2 + srp6Ptr + (ConstPtr aPtr) + aLen + outPtr + outLen + +-- | Generate a new SRP-6 verifier srp6GenerateVerifier :: Identifier -- ^ __identifier__: a username or other client identifier -> Password -- ^ __password__: the secret used to authenticate user @@ -244,28 +230,39 @@ srp6GenerateVerifier identifier password salt groupId hashId = asBytesLen salt $ \ saltPtr saltLen -> asCString groupId $ \ groupIdPtr -> asCString hashId $ \ hashIdPtr -> - allocBytesQuerying $ \ outPtr outLen -> - botan_srp6_generate_verifier - (ConstPtr identifierPtr) - (ConstPtr passwordPtr) - (ConstPtr saltPtr) - saltLen - (ConstPtr groupIdPtr) - (ConstPtr hashIdPtr) - outPtr - outLen - --- NOTE: ORDER IS DIFFERENT FROM SERVER GENERATE VERIFIER --- | SRP6a Client side step 2: Generate a client A-value and the client shared key + createWithGroupSize groupId $ \ outPtr outLen -> + throwBotanIfNegative_ $ + botan_srp6_generate_verifier + (ConstPtr identifierPtr) + (ConstPtr passwordPtr) + (ConstPtr saltPtr) + saltLen + (ConstPtr groupIdPtr) + (ConstPtr hashIdPtr) + outPtr + (castPtr outLen) + +-- | SRP6a Client side srp6ClientAgree :: - Identifier -- ^ __username__: the username we are attempting login for - -> Password -- ^ __password__: the password we are attempting to use - -> DLGroupName -- ^ __group_id__: specifies the shared SRP group - -> HashName -- ^ __hash_id__: specifies a secure hash function - -> Salt -- ^ __salt[]__: is the salt value sent by the server - -> SRP6BValue -- ^ __uint8_t__: B[] is the server's public value - -> RNG -- ^ __rng_obj__: is a random number generator object - -> IO (SRP6AValue, SRP6SharedSecret) -- @(A,K)@ + -- | __username__: the username we are attempting login for + Identifier + -- | __password__: the password we are attempting to use + -> Password + -- | __group_id__: specifies the shared SRP group + -> DLGroupName + -- | __hash_id__: specifies a secure hash function + -> HashName + -- | __salt[]__: is the salt value sent by the server + -> Salt + -- | __B[]__: is the server's public value + -> SRP6BValue + -- | __rng_obj__: is a random number generator object + -> RNG + -- | A tuple of two elements (in order): + -- + -- * __A[]__: out buffer to store the SRP-6 A value + -- * __K[]__: out buffer to store the symmetric value + -> IO (SRP6AValue, SRP6SharedSecret) srp6ClientAgree identifier password groupId hashId salt b rng = asCString identifier $ \ identifierPtr -> asCString password $ \ passwordPtr -> @@ -274,52 +271,89 @@ srp6ClientAgree identifier password groupId hashId salt b rng = asBytesLen salt $ \ saltPtr saltLen -> asBytesLen b $ \ bPtr bLen -> withRNG rng $ \ botanRNG -> - alloca $ \ aSzPtr -> - alloca $ \ kSzPtr -> do - -- Query sizes - -- TODO: Actually ensure expected error (insufficient buffer space) - -- and propagate unexpected errors - _ <- botan_srp6_client_agree - (ConstPtr identifierPtr) - (ConstPtr passwordPtr) - (ConstPtr groupIdPtr) - (ConstPtr hashIdPtr) - (ConstPtr saltPtr) - saltLen - (ConstPtr bPtr) - bLen - botanRNG - nullPtr - aSzPtr - nullPtr - kSzPtr - kSz <- fromIntegral <$> peek kSzPtr - aSz <- fromIntegral <$> peek aSzPtr - allocBytesWith kSz $ \ kPtr -> - allocBytes aSz $ \ aPtr -> - throwBotanIfNegative_ $ botan_srp6_client_agree - (ConstPtr identifierPtr) - (ConstPtr passwordPtr) - (ConstPtr groupIdPtr) - (ConstPtr hashIdPtr) - (ConstPtr saltPtr) - saltLen - (ConstPtr bPtr) - bLen - botanRNG - aPtr - aSzPtr - kPtr - kSzPtr - --- NOTE: Missing FFI function: srp6_group_identifierz + createWithGroupSize' groupId $ \ aPtr aSzPtr -> + createWithGroupSize groupId $ \ kPtr kSzPtr -> + throwBotanIfNegative_ $ + botan_srp6_client_agree + (ConstPtr identifierPtr) + (ConstPtr passwordPtr) + (ConstPtr groupIdPtr) + (ConstPtr hashIdPtr) + (ConstPtr saltPtr) + saltLen + (ConstPtr bPtr) + bLen + botanRNG + aPtr + aSzPtr + kPtr + kSzPtr -- | Return the size, in bytes, of the prime associated with group_id +-- +-- This function can be used to determine the size of output buffers for +-- generated keys in the SRP6 algorithm. Such buffers need to be allocated +-- before calling SRP6 functions. An example of such a buffer is the +-- @verifier[]@ buffer in the 'srp6GenerateVerifier' function. In @botan-low@, +-- such buffers are created internally using 'srp6GroupSize', without requiring +-- input from you (the user). srp6GroupSize :: DLGroupName -- ^ __group_id__ -> IO Int -- ^ __group_p_bytes__ srp6GroupSize groupId = asCString groupId $ \ groupIdPtr -> alloca $ \ szPtr -> do - throwBotanIfNegative_ $ botan_srp6_group_size (ConstPtr groupIdPtr) szPtr + throwBotanIfNegative_ $ + botan_srp6_group_size (ConstPtr groupIdPtr) szPtr fromIntegral <$> peek szPtr + +{------------------------------------------------------------------------------- + Utility +-------------------------------------------------------------------------------} + +-- | A version of 'BS.create' that determines the size of the byte string based +-- on an argument 'DLGroupName'. +createWithGroupSize :: + DLGroupName + -> (Ptr Word8 -> Ptr CSize -> IO ()) + -> IO ByteString +createWithGroupSize groupId k = do + sz <- srp6GroupSize groupId + createWithSize sz k + +-- | A version of 'BS.create' that also creates a pointer for the size of the +-- byte string. +createWithSize :: + Int + -> (Ptr Word8 -> Ptr CSize -> IO ()) + -> IO ByteString +createWithSize sz k = + BS.createUptoN sz $ \bytesPtr -> + alloca $ \lenPtr -> do + poke lenPtr (fromIntegral sz) + k bytesPtr lenPtr + fromIntegral <$> peek lenPtr + +-- | A version of 'BS.createUptoN'' that determines the size of the byte string +-- based on an argument 'DLGroupName'. +createWithGroupSize' :: + DLGroupName + -> (Ptr Word8 -> Ptr CSize -> IO a) + -> IO (ByteString, a) +createWithGroupSize' groupId k = do + sz <- srp6GroupSize groupId + createWithSize' sz k + +-- | A version of 'BS.createUptoN'' that also creates a pointer for the size of +-- the byte string. +createWithSize' :: + Int + -> (Ptr Word8 -> Ptr CSize -> IO a) + -> IO (ByteString, a) +createWithSize' sz k = + BS.createUptoN' sz $ \bytesPtr -> + alloca $ \lenPtr -> do + poke lenPtr (fromIntegral sz) + x <- k bytesPtr lenPtr + sz' <- fromIntegral <$> peek lenPtr + pure (sz', x) diff --git a/botan-low/test/Botan/Low/SRP6Spec.hs b/botan-low/test/Botan/Low/SRP6Spec.hs index 8403b60..1a5ec72 100644 --- a/botan-low/test/Botan/Low/SRP6Spec.hs +++ b/botan-low/test/Botan/Low/SRP6Spec.hs @@ -14,7 +14,7 @@ password :: ByteString password = "password" salt :: ByteString -salt = "salt" +salt = "saltsaltsaltsalt" -- NOTE: Consolidate with DLGroup groupIds :: [DLGroupName] @@ -55,6 +55,6 @@ main = hspec $ testSuite groupIds chars $ \ groupId -> do ctx <- srp6ServerSessionInit b <- srp6ServerSessionStep1 ctx verifier groupId hashId rng (a,sharedSecret) <- srp6ClientAgree username password groupId hashId salt b rng - sharedSecret' <- srp6ServerSessionStep2 ctx a + sharedSecret' <- srp6ServerSessionStep2 ctx groupId a sharedSecret `shouldBe` sharedSecret' pass diff --git a/botan/src/Botan/SRP6.hs b/botan/src/Botan/SRP6.hs index f475961..d224554 100644 --- a/botan/src/Botan/SRP6.hs +++ b/botan/src/Botan/SRP6.hs @@ -267,8 +267,9 @@ generateSRP6ClientKeys group hash ident pass salt skey = do generateSRP6SessionKey :: (MonadIO m) - => SRP6ServerSession + => SRP6Group + -> SRP6ServerSession -> SRP6ClientKey -> m SRP6SessionKey -generateSRP6SessionKey session ckey = liftIO $ Low.srp6ServerSessionStep2 session ckey +generateSRP6SessionKey group session ckey = liftIO $ Low.srp6ServerSessionStep2 session (dlGroupName group) ckey diff --git a/doc/ModuleHierarchy.md b/doc/ModuleHierarchy.md index 42c1f72..da11ac3 100644 --- a/doc/ModuleHierarchy.md +++ b/doc/ModuleHierarchy.md @@ -34,10 +34,11 @@ suffixes. Note that the C FFI documentation is not complete, so some Haskell modules export bindings to C entities that are not described in the C FFI documentation. -Such modules are instead based on the [C++ API reference][botan:api:pwdhash]. +Such modules are instead based on the [C++ API reference][botan:api]. Again, all three packages use the same module suffixes: * [Password Based Key Derivation][botan:api:pwdhash]: `PwdHash` +* [Secure Remote Password][botan:api:srp6]: `SRP6` Moreover, some modules export Haskell-only definitions that do not correspond directly to C entities, and some modules do not directly correspond to a C FFI or C++ @@ -55,3 +56,4 @@ as well. [botan:api]: https://botan.randombit.net/handbook/api_ref/contents.html [botan:api:pwdhash]: https://botan.randombit.net/handbook/api_ref/pbkdf.html +[botan:api:srp6]: https://botan.randombit.net/handbook/api_ref/srp.html From 28779643b3e009582bd5867cc67e58157eab8335 Mon Sep 17 00:00:00 2001 From: Joris Dral Date: Wed, 8 Oct 2025 09:46:40 +0200 Subject: [PATCH 6/7] `SRP6`: move `botan-low-pwdhash-tests` into the Tasty `test` component --- botan-low/botan-low.cabal | 22 +------------------ botan-low/test/Main.hs | 3 +++ .../SRP6Spec.hs => Test/Botan/Low/SRP6.hs} | 21 ++++++++++++++---- 3 files changed, 21 insertions(+), 25 deletions(-) rename botan-low/test/{Botan/Low/SRP6Spec.hs => Test/Botan/Low/SRP6.hs} (78%) diff --git a/botan-low/botan-low.cabal b/botan-low/botan-low.cabal index 2e5f780..a09d252 100644 --- a/botan-low/botan-low.cabal +++ b/botan-low/botan-low.cabal @@ -147,6 +147,7 @@ test-suite test other-modules: Test.Botan.Low.PwdHash + Test.Botan.Low.SRP6 Test.Prelude -- @@ -744,27 +745,6 @@ test-suite botan-low-rng-tests NoImplicitPrelude OverloadedStrings -test-suite botan-low-srp6-tests - import: warnings, language - type: exitcode-stdio-1.0 - main-is: Botan/Low/SRP6Spec.hs - hs-source-dirs: test/ - build-depends: - , base - , botan-low - , bytestring - , hspec - , QuickCheck - - other-modules: - Paths_botan_low - Test.Prelude - - autogen-modules: Paths_botan_low - default-extensions: - NoImplicitPrelude - OverloadedStrings - test-suite botan-low-totp-tests import: warnings, language type: exitcode-stdio-1.0 diff --git a/botan-low/test/Main.hs b/botan-low/test/Main.hs index 0dc7764..ad1e31d 100644 --- a/botan-low/test/Main.hs +++ b/botan-low/test/Main.hs @@ -1,6 +1,7 @@ module Main (main) where import qualified Test.Botan.Low.PwdHash +import qualified Test.Botan.Low.SRP6 import Test.Tasty main :: IO () @@ -9,6 +10,8 @@ main = tests >>= defaultMain tests :: IO TestTree tests = do pwdHashTests <- Test.Botan.Low.PwdHash.tests + srp6Tests <- Test.Botan.Low.SRP6.tests pure $ testGroup "botan-low" [ pwdHashTests + , srp6Tests ] diff --git a/botan-low/test/Botan/Low/SRP6Spec.hs b/botan-low/test/Test/Botan/Low/SRP6.hs similarity index 78% rename from botan-low/test/Botan/Low/SRP6Spec.hs rename to botan-low/test/Test/Botan/Low/SRP6.hs index 1a5ec72..f4b3064 100644 --- a/botan-low/test/Botan/Low/SRP6Spec.hs +++ b/botan-low/test/Test/Botan/Low/SRP6.hs @@ -1,11 +1,24 @@ -module Main (main) where +{-# LANGUAGE OverloadedStrings #-} -import Test.Prelude +module Test.Botan.Low.SRP6 ( + tests + ) where import Botan.Low.Hash import Botan.Low.PubKey import Botan.Low.RNG import Botan.Low.SRP6 +import Data.ByteString +import Test.Prelude +import Test.Tasty +import Test.Tasty.Hspec + +tests :: IO TestTree +tests = do + specs <- testSpec "spec_srp6" spec_srp6 + pure $ testGroup "Test.Botan.Low.SRP6" [ + specs + ] username :: ByteString username = "username" @@ -47,8 +60,8 @@ groupIds = hashId :: HashName hashId = "SHA-256" -main :: IO () -main = hspec $ testSuite groupIds chars $ \ groupId -> do +spec_srp6 :: Spec +spec_srp6 = testSuite groupIds chars $ \ groupId -> do it "can negotiate a shared secret" $ do rng <- rngInit "system" verifier <- srp6GenerateVerifier username password salt groupId hashId From b5ddc4ac0740638c9b718ce08fd63b212eda6379 Mon Sep 17 00:00:00 2001 From: Joris Dral Date: Wed, 8 Oct 2025 09:40:08 +0200 Subject: [PATCH 7/7] `SRP6`: add `CHANGELOG` entries --- botan-bindings/CHANGELOG.md | 1 + botan-low/CHANGELOG.md | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/botan-bindings/CHANGELOG.md b/botan-bindings/CHANGELOG.md index 14a7ff4..e5eb92f 100644 --- a/botan-bindings/CHANGELOG.md +++ b/botan-bindings/CHANGELOG.md @@ -19,6 +19,7 @@ * PATCH: enable `-Wall` in addition to a number of other GHC warnings. * PATCH: use `GHC2021` as the default language. * PATCH: update documentation in the `Botan.Bindings.PwdHash` module. +* PATCH: update documentation in the `Botan.Bindings.SRP6` module. ## 0.1.0.0 -- 2025-09-17 diff --git a/botan-low/CHANGELOG.md b/botan-low/CHANGELOG.md index 3e42769..7b6c85d 100644 --- a/botan-low/CHANGELOG.md +++ b/botan-low/CHANGELOG.md @@ -20,6 +20,13 @@ * BREAKING: `Botan.Low.PwdHash.pbkdf2` now takes a `HashName` instead of a `MacName`. * PATCH: update documentation in the `Botan.Low.PwdHash` module. +* PATCH: introduce a work-around for a bug found in + `Botan.Low.SRP6.srp6ServerSessionStep1`, which would always throw an + exception. See issue + [#28](https://github.com/haskell-cryptography/botan/issues/28). +* BREAKING: add a `DLGroupName` function argument to + `Botan.Low.SRP6.srp6ServerSessionStep2`. +* PATCH: update documentation in the `Botan.Low.SRP6` module. ## 0.0.2.0 -- 2025-09-17