tahoe-ssk-0.2.1.0: src/Tahoe/SDMF/Internal/Keys.hs
{- | Key types, derivations, and related functionality for SDMF.
See docs/specifications/mutable.rst for details.
-}
module Tahoe.SDMF.Internal.Keys where
import Prelude hiding (Read)
import Control.Monad (when)
import Crypto.Cipher.AES (AES128)
import Crypto.Cipher.Types (BlockCipher (ctrCombine), Cipher (cipherInit, cipherKeySize), IV, KeySizeSpecifier (KeySizeFixed), nullIV)
import Crypto.Error (CryptoFailable (CryptoPassed), maybeCryptoError)
import qualified Crypto.PubKey.RSA as RSA
import Crypto.Random (MonadRandom)
import Data.ASN1.BinaryEncoding (DER (DER))
import Data.ASN1.Encoding (ASN1Encoding (encodeASN1), decodeASN1')
import Data.ASN1.Types (ASN1 (End, IntVal, Null, OID, OctetString, Start), ASN1ConstructionType (Sequence), ASN1Object (fromASN1, toASN1))
import Data.Bifunctor (Bifunctor (first))
import Data.Binary (Binary (get, put))
import Data.Binary.Get (getByteString)
import Data.Binary.Put (putByteString)
import qualified Data.ByteArray as ByteArray
import qualified Data.ByteString as B
import Data.ByteString.Base32 (encodeBase32Unpadded)
import qualified Data.ByteString.Lazy as LB
import qualified Data.Text as T
import Data.X509 (PrivKey (PrivKeyRSA), PubKey (PubKeyRSA))
import Tahoe.CHK.Crypto (taggedHash, taggedPairHash)
newtype KeyPair = KeyPair {toPrivateKey :: RSA.PrivateKey} deriving newtype (Show)
toPublicKey :: KeyPair -> RSA.PublicKey
toPublicKey = RSA.private_pub . toPrivateKey
toSignatureKey :: KeyPair -> Signature
toSignatureKey = Signature . toPrivateKey
toVerificationKey :: KeyPair -> Verification
toVerificationKey = Verification . toPublicKey
newtype Verification = Verification {unVerification :: RSA.PublicKey}
deriving newtype (Eq, Show)
newtype Signature = Signature {unSignature :: RSA.PrivateKey}
deriving newtype (Eq, Show)
data Write = Write {unWrite :: AES128, writeKeyBytes :: ByteArray.ScrubbedBytes}
instance Eq Write where
(Write _ left) == (Write _ right) = left == right
instance Binary Write where
put = putByteString . ByteArray.convert . writeKeyBytes
get = do
writeKeyBytes <- ByteArray.convert <$> getByteString keyLength
let (CryptoPassed unWrite) = cipherInit writeKeyBytes
pure Write{..}
instance Show Write where
show (Write _ bs) =
T.unpack $
T.concat
[ "<WriteKey "
, shorten 4 . showBase32 . ByteArray.convert $ bs
, ">"
]
data Read = Read {unRead :: AES128, readKeyBytes :: ByteArray.ScrubbedBytes}
instance Eq Read where
(Read _ left) == (Read _ right) = left == right
instance Show Read where
show (Read _ bs) =
T.unpack $
T.concat
[ "<ReadKey "
, shorten 4 . showBase32 . ByteArray.convert $ bs
, ">"
]
instance Binary Read where
put = putByteString . ByteArray.convert . readKeyBytes
get = do
readKeyBytes <- ByteArray.convert <$> getByteString keyLength
let (CryptoPassed unRead) = cipherInit readKeyBytes
pure Read{..}
newtype StorageIndex = StorageIndex {unStorageIndex :: B.ByteString} deriving newtype (Eq, Ord)
instance Show StorageIndex where
show (StorageIndex si) =
T.unpack $
T.concat
[ "<SI "
, shorten 4 . showBase32 . ByteArray.convert $ si
, ">"
]
newtype WriteEnablerMaster = WriteEnablerMaster ByteArray.ScrubbedBytes
newtype WriteEnabler = WriteEnabler ByteArray.ScrubbedBytes
data Data = Data {unData :: AES128, dataKeyBytes :: ByteArray.ScrubbedBytes}
instance Show Data where
show (Data _ bs) =
T.unpack $
T.concat
[ "<DataKey "
, shorten 4 . showBase32 . ByteArray.convert $ bs
, ">"
]
instance Eq Data where
(Data _ left) == (Data _ right) = left == right
instance Binary Data where
put = putByteString . ByteArray.convert . dataKeyBytes
get = do
dataKeyBytes <- ByteArray.convert <$> getByteString keyLength
let (CryptoPassed unData) = cipherInit dataKeyBytes
pure Data{..}
newtype SDMF_IV = SDMF_IV (IV AES128)
deriving (Eq)
deriving newtype (ByteArray.ByteArrayAccess)
instance Show SDMF_IV where
show (SDMF_IV iv) = T.unpack . showBase32 . ByteArray.convert $ iv
-- | The size of the public/private key pair to generate.
keyPairBits :: Int
keyPairBits = 2048
-- | The number of bytes in the block cipher key.
keyLength :: Int
(KeySizeFixed keyLength) = cipherKeySize (undefined :: AES128)
{- | Create a new, random key pair (public/private aka verification/signature)
of the appropriate type and size for SDMF encryption.
-}
newKeyPair :: MonadRandom m => m KeyPair
newKeyPair = do
(_, priv) <- RSA.generate keyPairBits e
pure $ KeyPair priv
where
e = 0x10001
-- | Compute the write key for a given signature key for an SDMF share.
deriveWriteKey :: Signature -> Maybe Write
deriveWriteKey s =
Write <$> key <*> pure (ByteArray.convert sbs)
where
sbs = taggedHash keyLength mutableWriteKeyTag . signatureKeyToBytes $ s
key = maybeCryptoError . cipherInit $ sbs
mutableWriteKeyTag :: B.ByteString
mutableWriteKeyTag = "allmydata_mutable_privkey_to_writekey_v1"
-- | Compute the read key for a given write key for an SDMF share.
deriveReadKey :: Write -> Maybe Read
deriveReadKey w =
Read <$> key <*> pure (ByteArray.convert sbs)
where
sbs = taggedHash keyLength mutableReadKeyTag . ByteArray.convert . writeKeyBytes $ w
key = maybeCryptoError . cipherInit $ sbs
mutableReadKeyTag :: B.ByteString
mutableReadKeyTag = "allmydata_mutable_writekey_to_readkey_v1"
-- | Compute the data encryption/decryption key for a given read key for an SDMF share.
deriveDataKey :: SDMF_IV -> Read -> Maybe Data
deriveDataKey (SDMF_IV iv) r =
Data <$> key <*> pure (ByteArray.convert sbs)
where
-- XXX taggedPairHash has a bug where it doesn't ever truncate so we
-- truncate for it.
sbs = B.take keyLength . taggedPairHash keyLength mutableDataKeyTag (B.pack . ByteArray.unpack $ iv) . ByteArray.convert . readKeyBytes $ r
key = maybeCryptoError . cipherInit $ sbs
mutableDataKeyTag :: B.ByteString
mutableDataKeyTag = "allmydata_mutable_readkey_to_datakey_v1"
-- | Compute the storage index for a given read key for an SDMF share.
deriveStorageIndex :: Read -> StorageIndex
deriveStorageIndex r = StorageIndex si
where
si = taggedHash keyLength mutableStorageIndexTag . ByteArray.convert . readKeyBytes $ r
mutableStorageIndexTag :: B.ByteString
mutableStorageIndexTag = "allmydata_mutable_readkey_to_storage_index_v1"
{- | Derive the "write enabler master" secret for a given write key for an
SDMF share.
-}
deriveWriteEnablerMaster :: Write -> WriteEnablerMaster
deriveWriteEnablerMaster w = WriteEnablerMaster bs
where
-- This one shouldn't be truncated. Set the length to the size of sha256d
-- output.
bs = ByteArray.convert . taggedHash 32 mutableWriteEnablerMasterTag . ByteArray.convert . writeKeyBytes $ w
mutableWriteEnablerMasterTag :: B.ByteString
mutableWriteEnablerMasterTag = "allmydata_mutable_writekey_to_write_enabler_master_v1"
{- | Derive the "write enabler" secret for a given peer and "write enabler
master" for an SDMF share.
-}
deriveWriteEnabler :: B.ByteString -> WriteEnablerMaster -> WriteEnabler
deriveWriteEnabler peerid (WriteEnablerMaster master) = WriteEnabler bs
where
-- This one shouldn't be truncated. Set the length to the size of sha256d
-- output.
bs = ByteArray.convert . taggedPairHash 32 mutableWriteEnablerTag (ByteArray.convert master) $ peerid
mutableWriteEnablerTag :: B.ByteString
mutableWriteEnablerTag = "allmydata_mutable_write_enabler_master_and_nodeid_to_write_enabler_v1"
{- | Compute the verification key hash of the given verification key for
inclusion in an SDMF share.
-}
deriveVerificationHash :: Verification -> B.ByteString
deriveVerificationHash = taggedHash 32 mutableVerificationKeyHashTag . verificationKeyToBytes
{- | The tag used when hashing the verification key to the verification key
hash for inclusion in SDMF shares.
-}
mutableVerificationKeyHashTag :: B.ByteString
mutableVerificationKeyHashTag = "allmydata_mutable_pubkey_to_fingerprint_v1"
{- | Encode a public key to the Tahoe-LAFS canonical bytes representation -
X.509 SubjectPublicKeyInfo of the ASN.1 DER serialization of an RSA
PublicKey.
-}
verificationKeyToBytes :: Verification -> B.ByteString
verificationKeyToBytes = LB.toStrict . encodeASN1 DER . flip toASN1 [] . PubKeyRSA . unVerification
{- | Encode a private key to the Tahoe-LAFS canonical bytes representation -
X.509 SubjectPublicKeyInfo of the ASN.1 DER serialization of an RSA
PublicKey.
-}
signatureKeyToBytes :: Signature -> B.ByteString
signatureKeyToBytes = LB.toStrict . encodeASN1 DER . toPKCS8
where
-- The ASN1Object instance for PrivKeyRSA can interpret an x509
-- "Private-Key Information" (aka PKCS8; see RFC 5208, section 5)
-- structure but it _produces_ some other format. We must have exactly
-- this format.
--
-- XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
--
-- RFC 5208 says:
--
-- privateKey is an octet string whose contents are the value of the
-- private key. The interpretation of the contents is defined in the
-- registration of the private-key algorithm. For an RSA private key,
-- for example, the contents are a BER encoding of a value of type
-- RSAPrivateKey.
--
-- The ASN.1 BER encoding for a given structure is *not guaranteed to be
-- unique*. This means that in general there is no guarantee of a unique
-- bytes representation of a signature key in this scheme so *key
-- derivations are not unique*. If any two implementations disagree on
-- this encoding (which BER allows them to do) they will not interoperate.
toPKCS8 (Signature privKey) =
[ Start Sequence
, IntVal 0
, Start Sequence
, OID [1, 2, 840, 113549, 1, 1, 1]
, Null
, End Sequence
, -- Our ASN.1 encoder doesn't even pretend to support BER. Use DER!
-- It results in the same bytes as Tahoe-LAFS is working with so ...
-- Maybe we're lucky or maybe Tahoe-LAFS isn't actually following
-- the spec.
OctetString (LB.toStrict . encodeASN1 DER . toASN1 (PrivKeyRSA privKey) $ [])
, End Sequence
]
-- | Decode a private key from the Tahoe-LAFS canonical bytes representation.
signatureKeyFromBytes :: B.ByteString -> Either String Signature
signatureKeyFromBytes bs = do
asn1s <- first show $ decodeASN1' DER bs
(key, extra) <- fromASN1 asn1s
when (extra /= []) (Left $ "left over data: " <> show extra)
case key of
(PrivKeyRSA privKey) -> Right $ Signature privKey
_ -> Left ("Expect RSA private key, found " <> show key)
-- | Encrypt the signature key for inclusion in the SDMF share itself.
encryptSignatureKey :: Write -> Signature -> B.ByteString
encryptSignatureKey Write{unWrite} = ctrCombine unWrite nullIV . signatureKeyToBytes
{- | Replace most of the tail of a string with a short placeholder. If the
string is not much longer than `n` then the result might not actually be
shorter.
TODO: Deduplicate this between here and tahoe-chk.
-}
shorten :: Int -> T.Text -> T.Text
shorten n = (<> "...") . T.take n
showBase32 :: B.ByteString -> T.Text
showBase32 = T.toLower . encodeBase32Unpadded