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-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-bindings/src/Botan/Bindings/SRP6.hs b/botan-bindings/src/Botan/Bindings/SRP6.hs index 9901d18..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,79 +33,94 @@ 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) + = 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 + 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 - --- | SRP-6 Server side step 1: Generate a server B-value + botan_srp6_server_session_init + :: Ptr BotanSRP6ServerSession -- ^ __srp6__: SRP-6 server session object + -> IO CInt + +-- | 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 - -> 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 + 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 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 - --- | SRP-6 Client side step 1: Generate a new SRP-6 verifier + 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 + +-- | 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 - --- | SRP6a Client side step 2: Generate a client A-value and the client shared key + 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 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 -- ^ __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 +-- +-- 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__ - -> 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/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 diff --git a/botan-low/botan-low.cabal b/botan-low/botan-low.cabal index 7220e70..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,31 +745,6 @@ test-suite botan-low-rng-tests NoImplicitPrelude OverloadedStrings -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 - , 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/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/botan-low/src/Botan/Low/SRP6.hs b/botan-low/src/Botan/Low/SRP6.hs index f1a06e0..9be25d6 100644 --- a/botan-low/src/Botan/Low/SRP6.hs +++ b/botan-low/src/Botan/Low/SRP6.hs @@ -8,19 +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 @@ -30,16 +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 @@ -47,43 +39,19 @@ module Botan.Low.SRP6 ( , pattern MODP_SRP_4096 , pattern MODP_SRP_6144 , 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: @@ -183,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, _) @@ -193,134 +161,199 @@ 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 - --- | 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 - --- | 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 - --- 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 - --- NOTE: Missing FFI function: srp6_group_identifierz +-- | 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 + -> 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 -> + 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 groupId a = + withSRP6ServerSession srp6 $ \ srp6Ptr -> + asBytesLen a $ \ aPtr aLen -> + 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 + -> 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 -> + 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 :: + -- | __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 -> + asCString groupId $ \ groupIdPtr -> + asCString hashId $ \ hashIdPtr -> + asBytesLen salt $ \ saltPtr saltLen -> + asBytesLen b $ \ bPtr bLen -> + withRNG rng $ \ botanRNG -> + 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 -srp6GroupSize - :: DLGroupName -- ^ __group_id__ - -> IO Int -- ^ __group_p_bytes__ -srp6GroupSize groupId = asCString groupId $ \ groupIdPtr -> do +-- +-- 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 - fromIntegral <$> peek 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/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/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 () diff --git a/botan-low/test/Botan/Low/SRP6Spec.hs b/botan-low/test/Test/Botan/Low/SRP6.hs similarity index 73% rename from botan-low/test/Botan/Low/SRP6Spec.hs rename to botan-low/test/Test/Botan/Low/SRP6.hs index 481efd9..f4b3064 100644 --- a/botan-low/test/Botan/Low/SRP6Spec.hs +++ b/botan-low/test/Test/Botan/Low/SRP6.hs @@ -1,19 +1,24 @@ -{-# LANGUAGE CPP #-} +{-# LANGUAGE OverloadedStrings #-} -#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 +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" @@ -22,7 +27,7 @@ password :: ByteString password = "password" salt :: ByteString -salt = "salt" +salt = "saltsaltsaltsalt" -- NOTE: Consolidate with DLGroup groupIds :: [DLGroupName] @@ -51,21 +56,18 @@ groupIds = , "dsa/botan/3072" ] -groupId :: DLGroupName -groupId = head groupIds - -- TODO: Test which hashes work 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 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 5c63402..da11ac3 100644 --- a/doc/ModuleHierarchy.md +++ b/doc/ModuleHierarchy.md @@ -34,11 +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 -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]. +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,4 +55,5 @@ 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 +[botan:api:srp6]: https://botan.randombit.net/handbook/api_ref/srp.html