Z-Botan-0.4.0.0: Z/Crypto/SafeMem.hs
module Z.Crypto.SafeMem (
-- * Password
Password, mkPassword, mkPasswordMaybe, passwordSize, passwordToText
, withPasswordUnsafe, withPasswordSafe
, InvalidPasswordException(..)
-- * Nonce
, Nonce, rand96bitNonce, rand128bitNonce, rand192bitNonce
, cnt32bitNonce, cnt64bitNonce
-- * CEBytes
, CEBytes(..), ceBytesSize, ceBytesBitSize, newCEBytesUnsafe, newCEBytesSafe, ceBytes, unCEBytes
-- * Secret
, Secret, secretSize, secretBitSize, unsafeSecretFromBytes, unsafeSecretToBytes
, newSecret, withSecret
) where
import Control.Monad.Primitive
import Data.Bits
import Data.Char
import Data.Int
import Data.String
import Data.Word
import GHC.Prim
import GHC.Ptr
import Z.Botan.FFI
import Z.Crypto.RNG
import qualified Z.Data.Builder as B
import qualified Z.Data.CBytes as CB
import qualified Z.Data.Text as T
import qualified Z.Data.Text.Base as T
import qualified Z.Data.Vector.Base as V
import qualified Z.Data.Vector.Hex as V
import Z.Foreign
import Z.IO.Exception
import System.IO.Unsafe
-- | A type for human readable, it have
--
-- The 'Key' have the properties that:
--
-- * It's assumed to be UTF8 encoded and normalized, and does not have <https://en.wikipedia.org/wiki/Control_character control-characters>.
-- * There's no 'Eq' instance, you should always compare 'Password' via password hash.
-- * The 'Show' or 'Print' instance always print @"**PASSWORD**"@.
--
-- 'Password' is not intented to be saved or transmitted, it's only useful when you want to validate a user's input against password hash.
-- See "Z.Crypto.PwdHash".
--
newtype Password = Password CB.CBytes
instance Show Password where
show _ = "**PASSWORD**"
instance T.Print Password where
{-# INLINABLE toUTF8BuilderP #-}
toUTF8BuilderP _ _ = "**PASSWORD**"
instance IsString Password where
{-# INLINABLE fromString #-}
fromString = mkPassword . fromString
-- | Construct a password value from 'T.Text', if there're control-characters error will be thrown.
mkPassword :: HasCallStack => T.Text -> Password
{-# INLINABLE mkPassword #-}
mkPassword pwd = case mkPasswordMaybe pwd of
Just r -> r
_ -> throw (PasswordContainsControlCharacter callStack)
data InvalidPasswordException = PasswordContainsControlCharacter CallStack deriving Show
instance Exception InvalidPasswordException
-- | Construct a password value from 'Text', return 'Nothing' if contain control-characters.
mkPasswordMaybe :: T.Text -> Maybe Password
{-# INLINABLE mkPasswordMaybe #-}
mkPasswordMaybe pwd =
case T.find isControl pwd of
(_, Nothing) ->
let pwd' = case T.isNormalized pwd of
T.NormalizedYes -> pwd
_ -> T.normalize pwd
in Just $! Password (CB.fromText pwd')
_ -> Nothing
-- | Byte size of a password.
passwordSize :: Password -> Int
{-# INLINABLE passwordSize #-}
passwordSize (Password pwd) = CB.length pwd
-- | Get plaintext of a password.
passwordToText :: Password -> T.Text
{-# INLINABLE passwordToText #-}
passwordToText (Password pwd) = T.Text (CB.toBytes pwd)
-- | Use password as null-terminated @const char*@, USE WITH UNSAFE FFI ONLY, PLEASE DO NOT MODIFY THE CONTENT.
withPasswordUnsafe :: Password -> (BA# Word8 -> IO r) -> IO r
{-# INLINABLE withPasswordUnsafe #-}
withPasswordUnsafe (Password pwd) = CB.withCBytesUnsafe pwd
-- | Use password as null-terminated @const char*@, PLEASE DO NOT MODIFY THE CONTENT.
withPasswordSafe :: Password -> (Ptr Word8 -> IO r) -> IO r
{-# INLINABLE withPasswordSafe #-}
withPasswordSafe (Password pwd) = CB.withCBytes pwd
--------------------------------------------------------------------------------
-- | A value used only once in AEAD modes.
--
-- We use also this type to represent IV(initialization vector) for stream ciphers, but the way a nonce is generated is different:
-- random IV is one generation choice which is usually fine, while Nonce can also be a counter, which is not ok for CBC mode.
--
-- Some common nonce size:
--
-- * 96bit for GCM AEAD, ChaCha20Poly1305.
-- * 128bit for XChaCha20Poly1305.
-- * Block size for CBC IV(e.g. 128 bits for AES).
--
type Nonce = V.Bytes
-- | Get 64-bit random nonce.
rand96bitNonce :: RNG -> IO Nonce
{-# INLINABLE rand96bitNonce #-}
rand96bitNonce rng = getRandom rng 12
-- | Get 128-bit random nonce.
rand128bitNonce :: RNG -> IO Nonce
{-# INLINABLE rand128bitNonce #-}
rand128bitNonce rng = getRandom rng 16
-- | Get 192-bit random nonce.
rand192bitNonce :: RNG -> IO Nonce
{-# INLINABLE rand192bitNonce #-}
rand192bitNonce rng = getRandom rng 24
-- | Get 32bit nonce from counter.
cnt32bitNonce :: Int32 -> Nonce
{-# INLINABLE cnt32bitNonce #-}
cnt32bitNonce c = B.build $ B.encodePrimBE c
-- | Get 64bit nonce from counter.
cnt64bitNonce :: Int64 -> Nonce
{-# INLINABLE cnt64bitNonce #-}
cnt64bitNonce c = B.build $ B.encodePrimBE c
--------------------------------------------------------------------------------
-- | Constant-time equal comparing bytes.
--
-- It comes with following property:
--
-- * The 'Eq' instance gives you constant-time compare.
-- * The 'Show' and 'T.Print' instances give you hex encoding.
--
newtype CEBytes = CEBytes (PrimArray Word8)
ceBytesSize :: CEBytes -> Int
{-# INLINABLE ceBytesSize #-}
ceBytesSize (CEBytes d) = sizeofPrimArray d
ceBytesBitSize :: CEBytes -> Int
{-# INLINABLE ceBytesBitSize #-}
ceBytesBitSize (CEBytes d) = 8 * (V.length d)
instance Eq CEBytes where
{-# INLINABLE (==) #-}
(CEBytes pa@(PrimArray ba#)) == (CEBytes pb@(PrimArray bb#)) =
la == lb && botan_constant_time_compare_ba ba# bb# (fromIntegral la) == 0
where
la = sizeofPrimArray pa
lb = sizeofPrimArray pb
instance Show CEBytes where
show = T.toString
instance T.Print CEBytes where
toUTF8BuilderP _ = V.hexEncodeBuilder True . unCEBytes
-- | Create a ceBytes from unsafe FFI.
newCEBytesUnsafe :: Int -> (MBA# Word8 -> IO r) -> IO CEBytes
{-# INLINABLE newCEBytesUnsafe #-}
newCEBytesUnsafe len f = do
(d, _) <- allocPrimArrayUnsafe len f
pure (CEBytes d)
-- | Create a ceBytes from safe FFI.
newCEBytesSafe :: Int -> (Ptr Word8 -> IO r) -> IO CEBytes
{-# INLINABLE newCEBytesSafe #-}
newCEBytesSafe len f = do
(d, _) <- allocPrimArraySafe len f
pure (CEBytes d)
-- | Create a 'CEBytes' from 'V.Bytes'.
ceBytes :: V.Bytes -> CEBytes
{-# INLINABLE ceBytes #-}
ceBytes = CEBytes . V.arrVec
-- | Get 'CEBytes' 's content as 'V.Bytes', by doing this you lose the constant-time comparing.
unCEBytes :: CEBytes -> V.Bytes
{-# INLINABLE unCEBytes #-}
unCEBytes (CEBytes d) = V.arrVec d
--------------------------------------------------------------------------------
-- | Memory allocated by locked allocator and will be zeroed after used.
--
-- * It's allocated by botan's locking allocator(which means it will not get swapped to disk) if possible.
-- * It will zero the memory it used once get GCed.
-- * The 'Eq' instance gives you constant-time compare.
-- * The 'Show' or 'Print' instance always print @"**SECRET**"@.
--
-- 'Secret' is not intented to be saved or transmitted, there're several way to obtain a 'Secret':
--
-- + Use 'unsafeSecretFromBytes' to convert a piece of 'Bytes' to 'Secret'.
-- + Use key-exchanges from 'Z.Crypto.PubKey'.
-- + Unwrap a key, see 'Z.Crypto.KeyWrap'.
--
newtype Secret = Secret (PrimArray (Ptr Word8))
instance Show Secret where
show _ = "**SECRET**"
instance T.Print Secret where
{-# INLINABLE toUTF8BuilderP #-}
toUTF8BuilderP _ _ = "**SECRET**"
-- | This instance will break the no-tracing property by saving secret in compiled and loaded binary.
instance IsString Secret where
{-# INLINABLE fromString #-}
fromString = unsafePerformIO . unsafeSecretFromBytes . fromString
-- | Constant-time compare
instance Eq Secret where
{-# INLINABLE (==) #-}
a@(Secret pa) == b@(Secret pb) =
la == lb && botan_constant_time_compare (indexPrimArray pa 0) (indexPrimArray pb 0) (fromIntegral la) == 0
where
la = secretSize a
lb = secretSize b
-- | Get secret key's byte length.
secretSize :: Secret -> Int
{-# INLINABLE secretSize #-}
secretSize (Secret pa) = (indexPrimArray pa 1) `minusPtr` (indexPrimArray pa 0)
-- | Get secret key's bit size.
secretBitSize :: Secret -> Int
{-# INLINABLE secretBitSize #-}
secretBitSize k = secretSize k `unsafeShiftL` 3
-- | Unsafe convert a 'V.Bytes' to a 'Secret'.
--
-- Note the original 'V.Bytes' may get moved by GC or swapped to disk, which may defeat the purpose of using a 'Secret'.
unsafeSecretFromBytes :: V.Bytes -> IO Secret
{-# INLINABLE unsafeSecretFromBytes #-}
unsafeSecretFromBytes (V.PrimVector pa poff plen) = newSecret plen $ \ p ->
copyPrimArrayToPtr p pa poff plen
-- | Unsafe convert a 'V.Bytes' from a 'Secret'.
--
-- Note the result 'V.Bytes' may get moved by GC or swapped to disk, which may defeat the purpose of using a 'Secret'.
unsafeSecretToBytes :: Secret -> IO V.Bytes
{-# INLINABLE unsafeSecretToBytes #-}
unsafeSecretToBytes key = withSecret key $ \ p len ->
let len' = fromIntegral len
in fst <$> allocPrimVectorUnsafe len' (\ p' ->
copyPtrToMutablePrimArray (MutablePrimArray p') 0 p len')
-- | Initialize a 'Secret' which pass an allocated pointer pointing to zeros to a init function.
newSecret :: Int -> (Ptr Word8 -> IO r) -> IO Secret
{-# INLINABLE newSecret #-}
newSecret len f = mask_ $ do
mpa <- newPrimArray 2
p@(Ptr addr#) <- hs_botan_allocate_memory len
_ <- f p `onException` hs_botan_deallocate_memory (p `plusPtr` len) p
let !p'@(Ptr addr'#) = p `plusPtr` len
writePrimArray mpa 0 p
writePrimArray mpa 1 p'
pa@(PrimArray ba#) <- unsafeFreezePrimArray mpa
primitive_ $ \ s0# ->
let !(# s1#, w# #) = mkWeakNoFinalizer# ba# () s0#
!(# s2#, _ #) = addCFinalizerToWeak# fin# addr# 1# addr'# w# s1#
in s2#
return (Secret pa)
where
!(FunPtr fin#) = hs_botan_deallocate_memory_p
-- | Use 'Secret' as a @const char*@, PLEASE DO NOT MODIFY THE CONTENT.
--
withSecret :: Secret -> (Ptr Word8 -> CSize -> IO r) -> IO r
{-# INLINABLE withSecret #-}
withSecret (Secret pa@(PrimArray ba#)) f = do
let p = indexPrimArray pa 0
p' = indexPrimArray pa 1
x <- f p (fromIntegral $ p' `minusPtr` p)
primitive_ (touch# ba#)
return x