summaryrefslogtreecommitdiffhomepage
path: root/Encryption.hs
diff options
context:
space:
mode:
authorJoey Hess <joeyh@joeyh.name>2016-08-19 16:36:46 -0400
committerJoey Hess <joeyh@joeyh.name>2016-08-19 16:36:46 -0400
commit0afe2e6177b48078db381d26334d3f4fd13363da (patch)
treece23c42a9273394c0738978a6e0724d69b90777a /Encryption.hs
parentfdc80b7a2416782d3208acf154fb8afb7fb2279b (diff)
downloadkeysafe-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.hs129
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)