diff options
author | Joey Hess <joeyh@joeyh.name> | 2016-08-19 16:36:46 -0400 |
---|---|---|
committer | Joey Hess <joeyh@joeyh.name> | 2016-08-19 16:36:46 -0400 |
commit | 0afe2e6177b48078db381d26334d3f4fd13363da (patch) | |
tree | ce23c42a9273394c0738978a6e0724d69b90777a /Encryption.hs | |
parent | fdc80b7a2416782d3208acf154fb8afb7fb2279b (diff) | |
download | keysafe-0afe2e6177b48078db381d26334d3f4fd13363da.tar.gz |
chunking
This changed the storage format, not that it matters because nobody is
using it yet.
Diffstat (limited to 'Encryption.hs')
-rw-r--r-- | Encryption.hs | 129 |
1 files changed, 94 insertions, 35 deletions
diff --git a/Encryption.hs b/Encryption.hs index 4a8f5cf..fbeb1c6 100644 --- a/Encryption.hs +++ b/Encryption.hs @@ -21,6 +21,7 @@ import qualified Raaz.Cipher.Internal as Raaz import qualified Data.Text.Encoding as E import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as B8 +import qualified Data.ByteString.UTF8 as BU8 import Text.Read type AesKey = Raaz.KEY256 @@ -29,27 +30,43 @@ cipher :: Raaz.AES 256 'Raaz.CBC cipher = Raaz.aes256cbc encrypt :: Tunables -> KeyEncryptionKey -> SecretKey -> EncryptedSecretKey -encrypt tunables kek (SecretKey secret) = EncryptedSecretKey b (keyBruteForceCalc kek) +encrypt tunables kek (SecretKey secret) = + EncryptedSecretKey (chunk (objectSize tunables) b) (keyBruteForceCalc kek) where -- Raaz does not seem to provide a high-level interface -- for AES encryption, so use unsafeEncrypt. The use of -- EncryptableBytes makes sure it's provided with a -- multiple of the AES block size. b = Raaz.unsafeEncrypt cipher (keyEncryptionKey kek, keyEncryptionIV kek) $ - getEncryptableBytes $ toEncryptableBytes tunables secret + getEncryptableBytes $ encodeEncryptableBytes tunables secret -decrypt :: Candidates KeyEncryptionKey -> EncryptedSecretKey -> Maybe SecretKey -decrypt (Candidates l _ _) esk = go l +data DecryptResult + = DecryptSuccess SecretKey + | DecryptIncomplete KeyEncryptionKey + | DecryptFailed + +-- | Tries each candidate key in turn until one unlocks the encrypted data. +-- +-- When the EncryptedSecretKey is truncated, returns IncompleteDecrypt. +-- This avoids needing to try the candidate keys again after retrieving +-- more chunks. +tryDecrypt :: Candidates KeyEncryptionKey -> EncryptedSecretKey -> DecryptResult +tryDecrypt (Candidates l _ _) esk = go l where - go [] = Nothing - go (kek:rest) = case decrypt' kek esk of - Just sk -> Just sk - Nothing -> go rest + go [] = DecryptFailed + go (kek:rest) = case decrypt kek esk of + DecryptFailed -> go rest + r -> r -decrypt' :: KeyEncryptionKey -> EncryptedSecretKey -> Maybe SecretKey -decrypt' kek (EncryptedSecretKey b _) = SecretKey <$> fromEncryptableBytes pbs +decrypt :: KeyEncryptionKey -> EncryptedSecretKey -> DecryptResult +decrypt kek (EncryptedSecretKey cs _) = case decodeEncryptableBytes pbs of + Nothing -> DecryptFailed + Just (DecodeSuccess secretkey) -> DecryptSuccess (SecretKey secretkey) + Just DecodeIncomplete -> DecryptIncomplete kek where - pbs = EncryptableBytes $ Raaz.unsafeDecrypt cipher (keyEncryptionKey kek, keyEncryptionIV kek) b + pbs = EncryptableBytes $ + Raaz.unsafeDecrypt cipher (keyEncryptionKey kek, keyEncryptionIV kek) b + b = B.concat cs -- | An AES key, which is used to encrypt the secret key that is stored -- in keysafe. @@ -128,6 +145,15 @@ allByteStringsOfLength = go [] w <- [0..255] go (w:ws) (n-1) +chunk :: Int -> B.ByteString -> [B.ByteString] +chunk n = go [] + where + go cs b + | B.length b <= n = b : reverse cs + | otherwise = + let (h, t) = B.splitAt n b + in go (h:cs) t + -- Use the sha256 of the name (truncated) as the IV. genIV :: Name -> Raaz.IV genIV (Name name) = @@ -164,23 +190,45 @@ hashToAESKey (ExpensiveHash _ t) = b = B.take (fromIntegral $ Raaz.byteSize (undefined :: AesKey)) $ Raaz.toByteString $ Raaz.sha256 (E.encodeUtf8 t) --- | A bytestring that can be AES encrypted. It includes a checksum, --- and size, and is padded to the objectSize with NULs. +-- | A bytestring that can be AES encrypted. +-- +-- It is padded to a multiple of the objectSize with NULs. +-- Since objectSize is a multiple of the AES blocksize, so is this. +-- +-- Format is: +-- +-- sizeNULsizeshaNULdatashaNULdata -- --- This is a multiple of the AES blocksize, as long as objectSize is, --- which should always be the case. +-- The size gives the length of the data. If the data is shorter +-- than that, we know that the bytestring is truncated. +-- +-- The datasha is the sha256 of the data. This is checked when decoding +-- to guard against corruption. +-- +-- The sizesha is the sha256 of the size. This is included as a sanity +-- check that the right key was used to decrypt it. It's not unlikely +-- that using the wrong key could result in a bytestring that starts +-- with wrongsizeNUL, but it's astronomically unlikely that the +-- sizesha matches in this case. newtype EncryptableBytes = EncryptableBytes { getEncryptableBytes :: B.ByteString } deriving (Show) -toEncryptableBytes :: Tunables -> B.ByteString -> EncryptableBytes -toEncryptableBytes tunables b = EncryptableBytes $ - padBytes (objectSize tunables) $ - checksum <> sep <> len <> sep <> b +encodeEncryptableBytes :: Tunables -> B.ByteString -> EncryptableBytes +encodeEncryptableBytes tunables content = EncryptableBytes $ + padBytes (objectSize tunables) $ B.intercalate sep + [ size + , sha size + , sha content + , content + ] where - checksum = Raaz.toByteString $ Raaz.sha256 b - len = B8.pack (show (B.length b)) + size = B8.pack (show (B.length content)) sep = B.singleton 0 +-- | Encoded, so that it does not contain any NULs. +sha :: B.ByteString -> B.ByteString +sha = BU8.fromString . Raaz.showBase16 . Raaz.sha256 + padBytes :: Int -> B.ByteString -> B.ByteString padBytes n b = b <> padding where @@ -190,17 +238,28 @@ padBytes n b = b <> padding | r == 0 = B.empty | otherwise = B.replicate (n - r) 0 -fromEncryptableBytes :: EncryptableBytes -> Maybe B.ByteString -fromEncryptableBytes (EncryptableBytes b) = case B.break (== 0) b of - (checksum, rest) - | B.null checksum || B.null rest -> Nothing - | otherwise -> do - case B.break (== 0) (B.drop 1 rest) of - (lenb, rest') - | B.null lenb || B.null rest' -> Nothing - | otherwise -> do - len <- readMaybe (B8.unpack lenb) - let d = B.take len $ B.drop 1 rest' - if checksum == Raaz.toByteString (Raaz.sha256 d) - then Just d - else Nothing +data DecodeResult + = DecodeSuccess B.ByteString + | DecodeIncomplete + deriving (Show) + +decodeEncryptableBytes :: EncryptableBytes -> Maybe DecodeResult +decodeEncryptableBytes (EncryptableBytes b) = do + (sizeb, rest) <- getword b + (sizesha, rest') <- getword rest + (contentsha, rest'') <- getword rest' + if sha sizeb /= sizesha + then Nothing + else do + size <- readMaybe (B8.unpack sizeb) + let content = B.take size rest'' + if B.length content /= size + then return DecodeIncomplete + else if sha content /= contentsha + then Nothing + else return (DecodeSuccess content) + where + getword d = case B.break (== 0) d of + (w, rest) + | B.null w || B.null rest-> Nothing + | otherwise -> Just (w, B.drop 1 rest) |