diff --git a/make-keypairs/Main.hs b/make-keypairs/Main.hs index d77171e5303c5fedfedabacef1830f9c687e5581..3e5eea01f14048b4fca3c12a2d041a2be5de5fa6 100644 --- a/make-keypairs/Main.hs +++ b/make-keypairs/Main.hs @@ -1,11 +1,9 @@ module Main where import qualified Crypto.PubKey.RSA as RSA -import Data.ASN1.BinaryEncoding (DER (DER)) -import Data.ASN1.Encoding (ASN1Encoding (encodeASN1)) -import Data.ASN1.Types (ASN1Object (toASN1)) -import qualified Data.ByteString.Lazy as LB -import Data.X509 (PrivKey (PrivKeyRSA)) +import qualified Data.ByteString as B +import Tahoe.SDMF.Internal.Keys (signatureKeyToBytes) +import Tahoe.SDMF.Keys (Signature (..)) -- | The size of the keys to generate. bits :: Int @@ -21,8 +19,12 @@ main = do genKey :: Show a => a -> IO () genKey n = do - (_, priv) <- RSA.generate bits e - let bytes = encodeASN1 DER (toASN1 (PrivKeyRSA priv) []) - LB.writeFile ("test/data/rsa-privkey-" <> show n <> ".der") bytes + print "Generating RSA key..." + (_, priv) <- RSA.generate (bits `div` 8) e + print $ "Serializing key " <> show n + let bytes = signatureKeyToBytes (Signature priv) + print $ "Generated them (" <> show (B.length bytes) <> " bytes)" + B.writeFile ("test/data/rsa-privkey-" <> show n <> ".der") bytes + print "Wrote them to the file." where e = 0x10001 diff --git a/src/Tahoe/SDMF/Internal/Keys.hs b/src/Tahoe/SDMF/Internal/Keys.hs new file mode 100644 index 0000000000000000000000000000000000000000..e40d6fbefa909e638a689f7d802a3b7ee53bc1d4 --- /dev/null +++ b/src/Tahoe/SDMF/Internal/Keys.hs @@ -0,0 +1,200 @@ +{- | 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 (Cipher (cipherInit, cipherKeySize), IV, KeySizeSpecifier (KeySizeFixed)) +import Crypto.Error (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 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 + +newtype Verification = Verification {unVerification :: RSA.PublicKey} +newtype Signature = Signature {unSignature :: RSA.PrivateKey} + deriving newtype (Eq, Show) + +data Write = Write {unWrite :: AES128, writeKeyBytes :: ByteArray.ScrubbedBytes} +instance Show Write where + show (Write _ bs) = T.unpack $ T.concat ["<WriteKey ", encodeBase32Unpadded (ByteArray.convert bs), ">"] + +data Read = Read {unRead :: AES128, readKeyBytes :: ByteArray.ScrubbedBytes} +newtype StorageIndex = StorageIndex {unStorageIndex :: B.ByteString} + +newtype WriteEnablerMaster = WriteEnablerMaster ByteArray.ScrubbedBytes + +newtype WriteEnabler = WriteEnabler ByteArray.ScrubbedBytes + +data Data = Data {unData :: AES128, dataKeyBytes :: ByteArray.ScrubbedBytes} + +newtype SDMF_IV = SDMF_IV (IV AES128) + deriving (Eq) + deriving newtype (ByteArray.ByteArrayAccess) + +instance Show SDMF_IV where + show (SDMF_IV iv) = T.unpack . T.toLower . encodeBase32Unpadded . ByteArray.convert $ iv + +-- | The size of the public/private key pair to generate. +keyPairBits :: Int +keyPairBits = 2048 + +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" + +{- | 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) diff --git a/src/Tahoe/SDMF/Internal/Share.hs b/src/Tahoe/SDMF/Internal/Share.hs index 989f0714f57be13589de2ceb8fd6e3b36d9554b3..8d90b2aaf21576cde2a5d9116c1342187b0e4aeb 100644 --- a/src/Tahoe/SDMF/Internal/Share.hs +++ b/src/Tahoe/SDMF/Internal/Share.hs @@ -13,12 +13,11 @@ import Data.Binary.Get (bytesRead, getByteString, getLazyByteString, getRemainin import Data.Binary.Put (putByteString, putLazyByteString, putWord16be, putWord32be, putWord64be, putWord8) 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.Word (Word16, Word64, Word8) import Data.X509 (PubKey (PubKeyRSA)) import Tahoe.CHK.Merkle (MerkleTree, leafHashes) +import Tahoe.SDMF.Internal.Keys (SDMF_IV (..)) hashSize :: Int hashSize = 32 @@ -89,13 +88,6 @@ data Share = Share } deriving (Eq, Show) -newtype SDMF_IV = SDMF_IV (IV AES128) - deriving (Eq) - deriving newtype (ByteArray.ByteArrayAccess) - -instance Show SDMF_IV where - show (SDMF_IV iv) = T.unpack . T.toLower . encodeBase32Unpadded . ByteArray.convert $ iv - instance Binary Share where put Share{..} = do putWord8 0 diff --git a/src/Tahoe/SDMF/Keys.hs b/src/Tahoe/SDMF/Keys.hs new file mode 100644 index 0000000000000000000000000000000000000000..165915612de3689305539139b0c0e53807b323da --- /dev/null +++ b/src/Tahoe/SDMF/Keys.hs @@ -0,0 +1,20 @@ +module Tahoe.SDMF.Keys (module Tahoe.SDMF.Internal.Keys) where + +import Tahoe.SDMF.Internal.Keys ( + Data (..), + KeyPair (..), + Read (..), + SDMF_IV (..), + Signature (..), + StorageIndex (..), + Write (..), + WriteEnabler (..), + WriteEnablerMaster (..), + deriveDataKey, + deriveReadKey, + deriveStorageIndex, + deriveWriteEnabler, + deriveWriteEnablerMaster, + deriveWriteKey, + toPublicKey, + ) diff --git a/tahoe-ssk.cabal b/tahoe-ssk.cabal index 7a873bea65afab04a5eabd94b1b6f4a776218664..d7e3e7b2fa120ba97e87eda4899e187d4bda37ad 100644 --- a/tahoe-ssk.cabal +++ b/tahoe-ssk.cabal @@ -1,4 +1,4 @@ -cabal-version: 2.4 +cabal-version: 2.4 -- The cabal-version field refers to the version of the .cabal specification, -- and can be different from the cabal-install (the tool) version and the @@ -12,7 +12,7 @@ cabal-version: 2.4 -- http://haskell.org/cabal/users-guide/ -- -- The name of the package. -name: tahoe-ssk +name: tahoe-ssk -- The package version. -- See the Haskell package versioning policy (PVP) for standards @@ -21,7 +21,7 @@ name: tahoe-ssk -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change -version: 0.1.0.0 +version: 0.1.0.0 -- A short (one-line) description of the package. synopsis: @@ -31,30 +31,31 @@ synopsis: -- description: -- URL for the project homepage or repository. -homepage: https://whetstone.private.storage/PrivateStorage/tahoe-ssk +homepage: https://whetstone.private.storage/PrivateStorage/tahoe-ssk -- The license under which the package is released. -license: BSD-3-Clause +license: BSD-3-Clause -- The file containing the license text. -license-file: LICENSE +license-file: LICENSE -- The package author(s). -author: Jean-Paul Calderone +author: Jean-Paul Calderone -- An email address to which users can send suggestions, bug reports, and patches. -maintainer: jean-paul@private.storage +maintainer: jean-paul@private.storage -- A copyright notice. -- copyright: -category: Cryptography,Library,Parsers,Security -build-type: Simple +category: Cryptography,Library,Parsers,Security +build-type: Simple -- Extra doc files to be distributed with the package, such as a CHANGELOG or a README. -extra-doc-files: CHANGELOG.md +extra-doc-files: CHANGELOG.md --- Extra source files to be distributed with the package, such as examples, or a tutorial module. --- extra-source-files: +-- Extra source files to be distributed with the package, such as examples, or +-- a tutorial module. In our case, test data. +extra-source-files: test/data/* common warnings ghc-options: -Wall @@ -63,7 +64,9 @@ library hs-source-dirs: src exposed-modules: Tahoe.SDMF + Tahoe.SDMF.Internal.Keys Tahoe.SDMF.Internal.Share + Tahoe.SDMF.Keys build-depends: , asn1-encoding @@ -120,15 +123,18 @@ test-suite tahoe-ssk-test , asn1-encoding , asn1-types , base ^>=4.14.3.0 + , base32 , binary , bytestring , cryptonite , hedgehog + , memory , tahoe-chk , tahoe-ssk , tasty , tasty-hedgehog , tasty-hunit + , text , x509 -- A helper for generating RSA key pairs for use by the test suite. @@ -143,4 +149,5 @@ executable make-keypairs , base , bytestring , cryptonite + , tahoe-ssk , x509 diff --git a/test/Generators.hs b/test/Generators.hs index be8e2b78e313ddbaff67c27831d3837bb1fca991..21bee5bb4b2c369d5f52d99e48feecb242cecbdb 100644 --- a/test/Generators.hs +++ b/test/Generators.hs @@ -3,7 +3,6 @@ module Generators where import Crypto.Cipher.Types (makeIV) import Crypto.Hash (HashAlgorithm (hashDigestSize)) import Crypto.Hash.Algorithms (SHA256 (SHA256)) -import qualified Crypto.PubKey.RSA as RSA import Data.ASN1.BinaryEncoding (DER (DER)) import Data.ASN1.Encoding (ASN1Decoding (decodeASN1), ASN1Encoding (encodeASN1)) import Data.ASN1.Types (ASN1Object (fromASN1, toASN1)) @@ -17,7 +16,8 @@ import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range import Tahoe.CHK.Merkle (MerkleTree (..), makeTreePartial) import Tahoe.SDMF (Share (..)) -import Tahoe.SDMF.Internal.Share (HashChain (HashChain), SDMF_IV (..)) +import Tahoe.SDMF.Internal.Share (HashChain (HashChain)) +import qualified Tahoe.SDMF.Keys as Keys rootHashLength :: Int rootHashLength = 32 @@ -28,10 +28,6 @@ ivLength = 16 signatureLength :: Range.Range Int signatureLength = Range.linear 250 260 -newtype KeyPair = KeyPair RSA.PrivateKey -toPrivateKey (KeyPair privKey) = privKey -toPublicKey = RSA.private_pub . toPrivateKey - {- | Generate SDMF shares. The contents of the share are not necessarily semantically valid. -} @@ -45,17 +41,17 @@ shares = do Share <$> Gen.word64 Range.exponentialBounded -- shareSequenceNumber <*> Gen.bytes (Range.singleton rootHashLength) -- shareRootHash - <*> pure (SDMF_IV iv') -- shareIV + <*> pure (Keys.SDMF_IV iv') -- shareIV <*> Gen.word8 Range.exponentialBounded -- shareTotalShares <*> Gen.word8 Range.exponentialBounded -- shareRequiredShares <*> Gen.word64 Range.exponentialBounded -- shareSegmentSize <*> Gen.word64 Range.exponentialBounded -- shareDataLength - <*> pure (toPublicKey keypair) -- shareVerificationKey + <*> pure (Keys.toPublicKey keypair) -- shareVerificationKey <*> Gen.bytes signatureLength -- shareSignature <*> shareHashChains -- shareHashChain <*> merkleTrees (Range.singleton 1) -- shareBlockHashTree <*> (LB.fromStrict <$> Gen.bytes (Range.exponential 0 1024)) -- shareData - <*> (pure . LB.toStrict . toDER . PrivKeyRSA . toPrivateKey) keypair -- shareEncryptedPrivateKey + <*> (pure . LB.toStrict . toDER . PrivKeyRSA . Keys.toPrivateKey) keypair -- shareEncryptedPrivateKey where toDER = encodeASN1 DER . flip toASN1 [] @@ -67,7 +63,7 @@ shares = do challenging, this implementation just knows a few RSA key pairs already and will give back one of them. -} -genRSAKeys :: MonadGen m => m KeyPair +genRSAKeys :: MonadGen m => m Keys.KeyPair genRSAKeys = Gen.element (map rsaKeyPair rsaKeyPairBytes) -- I'm not sure how to do IO in MonadGen so do the IO up front unsafely (but @@ -76,13 +72,13 @@ rsaKeyPairBytes :: [LB.ByteString] {-# NOINLINE rsaKeyPairBytes #-} rsaKeyPairBytes = unsafePerformIO $ mapM (\n -> LB.readFile ("test/data/rsa-privkey-" <> show n <> ".der")) [0 .. 4 :: Int] -rsaKeyPair :: LB.ByteString -> KeyPair +rsaKeyPair :: LB.ByteString -> Keys.KeyPair rsaKeyPair bs = do let (Right kp) = do asn1s <- first show (decodeASN1 DER bs) (r, _) <- fromASN1 asn1s case r of - PrivKeyRSA pk -> pure $ KeyPair pk + PrivKeyRSA pk -> pure $ Keys.KeyPair pk _ -> error "Expected RSA Private Key" kp diff --git a/test/Spec.hs b/test/Spec.hs index 98914dd558e645bf065f40a11f6e8d49e9212e4d..e185dc49dcb1d803fa8f0b21ec09d97bc8fe549d 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -6,16 +6,29 @@ import Hedgehog ( tripping, ) +import Crypto.Cipher.Types (makeIV) +import Data.ASN1.BinaryEncoding (DER (DER)) +import Data.ASN1.Encoding (decodeASN1') import qualified Data.Binary as Binary import Data.Binary.Get (ByteOffset) +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 Generators (shareHashChains, shares) +import qualified Data.Text as T +import Generators (genRSAKeys, shareHashChains, shares) import System.IO (hSetEncoding, stderr, stdout, utf8) import Tahoe.SDMF (Share) +import Tahoe.SDMF.Internal.Keys (signatureKeyFromBytes, signatureKeyToBytes) +import qualified Tahoe.SDMF.Keys as Keys import Test.Tasty (TestTree, defaultMain, testGroup) import Test.Tasty.HUnit (assertEqual, testCase) import Test.Tasty.Hedgehog (testProperty) +-- The test suite compares against some hard-coded opaque strings. These +-- expected values were determined using the expected_values.py program in +-- this directory. + tests :: TestTree tests = testGroup @@ -24,6 +37,102 @@ tests = property $ do hashChain <- forAll shareHashChains tripping hashChain Binary.encode decode' + , testProperty "Signatures round-trip through signatureKeyToBytes . signatureKeyFromBytes" $ + property $ do + key <- forAll genRSAKeys + tripping (Keys.Signature . Keys.toPrivateKey $ key) signatureKeyToBytes signatureKeyFromBytes + , testCase "Signature byte-serializations round-trip through signatureKeyFromBytes . signatureKeyToBytes" $ do + let keyPaths = + [ -- Check ours + "test/data/rsa-privkey-0.der" + , "test/data/rsa-privkey-1.der" + , "test/data/rsa-privkey-2.der" + , "test/data/rsa-privkey-3.der" + , "test/data/rsa-privkey-4.der" + , -- And one from Tahoe-LAFS + "test/data/tahoe-lafs-generated-rsa-privkey.der" + ] + checkSignatureRoundTrip p = + B.readFile p >>= \original -> + let (Right sigKey) = signatureKeyFromBytes original + serialized = signatureKeyToBytes sigKey + in do + -- They should decode to the same structure. This + -- has the advantage of representing differences a + -- little more transparently than the next + -- assertion. + assertEqual + "decodeASN1 original /= decodeASN1 serialized" + (decodeASN1' DER original) + (decodeASN1' DER serialized) + -- Also check the raw bytes in case there + -- are different representations of the + -- structure possible. The raw bytes + -- matter because we hash them in key + -- derivations. + assertEqual "original /= serialized" original serialized + -- Check them all + mapM_ checkSignatureRoundTrip keyPaths + , testCase "derived keys equal known-correct values" $ + -- The path is relative to the root of the package, which is where + -- at least some test runners will run the test process. If + B.readFile "test/data/rsa-privkey-0.der" >>= \privBytes -> + let -- Load the test key. + (Right sigKey) = signatureKeyFromBytes privBytes + + -- Hard-code the expected result. + expectedWriteKey = ("v7iymuxkc5yv2fomi3xwbjdd4e" :: T.Text) + expectedReadKey = ("6ir6husgx6ubro3tbimmzskqri" :: T.Text) + expectedDataKey = ("bbj67exlrkfcaqutwlgwvukbfe" :: T.Text) + expectedStorageIndex = ("cmkuloz2t6fhsh7npxxteba6sq" :: T.Text) + expectedWriteEnablerMaster = ("qgptod5dsanfep2kbimvxl2yixndnoks7ndoeamczj7g33gokcvq" :: T.Text) + expectedWriteEnabler = ("bg4ldrgfyiffufltcuttr3cnrmrjfpoxc65qdoqa6d5izkzofl5q" :: T.Text) + + -- Constants involved in the derivation. These agree with + -- those used to generate the above expected values. + (Just iv) = Keys.SDMF_IV <$> makeIV (B.replicate 16 0x42) + peerid = B.replicate 20 0x42 + + -- Derive all the keys. + (Just w@(Keys.Write _ derivedWriteKey)) = Keys.deriveWriteKey sigKey + (Just r@(Keys.Read _ derivedReadKey)) = Keys.deriveReadKey w + (Just (Keys.Data _ derivedDataKey)) = Keys.deriveDataKey iv r + (Keys.StorageIndex derivedStorageIndex) = Keys.deriveStorageIndex r + wem@(Keys.WriteEnablerMaster derivedWriteEnablerMaster) = Keys.deriveWriteEnablerMaster w + (Keys.WriteEnabler derivedWriteEnabler) = Keys.deriveWriteEnabler peerid wem + -- A helper to format a key as text for convenient + -- comparison to expected value. + fmtKey = T.toLower . encodeBase32Unpadded . ByteArray.convert + in do + -- In general it might make more sense to convert expected + -- into ScrubbedBytes instead of converting derived into + -- ByteString but ScrubbedBytes doesn't have a useful Show + -- instance so we go the other way. We're not worried about + -- the safety of these test-only keys anyway. + assertEqual + "writekey: expected /= derived" + expectedWriteKey + (fmtKey derivedWriteKey) + assertEqual + "readkey: expected /= derived" + expectedReadKey + (fmtKey derivedReadKey) + assertEqual + "datakey: expected /= derived" + expectedDataKey + (fmtKey derivedDataKey) + assertEqual + "storage index: expected /= derived" + expectedStorageIndex + (T.toLower . encodeBase32Unpadded $ derivedStorageIndex) + assertEqual + "write enabler master: expected /= derived" + expectedWriteEnablerMaster + (fmtKey derivedWriteEnablerMaster) + assertEqual + "write enabler: expected /= derived" + expectedWriteEnabler + (fmtKey derivedWriteEnabler) , testProperty "Share round-trips through bytes" $ property $ do share <- forAll shares diff --git a/test/data/rsa-privkey-0.der b/test/data/rsa-privkey-0.der index ad64c6304d32d06ba74f9a9234de2cd14a6820ab..e3bb393ed5637f17db469248532b44420cfefc8f 100644 Binary files a/test/data/rsa-privkey-0.der and b/test/data/rsa-privkey-0.der differ diff --git a/test/data/rsa-privkey-1.der b/test/data/rsa-privkey-1.der index ad64c6304d32d06ba74f9a9234de2cd14a6820ab..9bffed68a1b2dcae3bb7a666d71540b26fbfd8d8 100644 Binary files a/test/data/rsa-privkey-1.der and b/test/data/rsa-privkey-1.der differ diff --git a/test/data/rsa-privkey-2.der b/test/data/rsa-privkey-2.der index ad64c6304d32d06ba74f9a9234de2cd14a6820ab..df86447a4ead9bde2e5000614e59cd2149ae505e 100644 Binary files a/test/data/rsa-privkey-2.der and b/test/data/rsa-privkey-2.der differ diff --git a/test/data/rsa-privkey-3.der b/test/data/rsa-privkey-3.der index ad64c6304d32d06ba74f9a9234de2cd14a6820ab..b8a87219665ec0f04855d05596603032725e0d36 100644 Binary files a/test/data/rsa-privkey-3.der and b/test/data/rsa-privkey-3.der differ diff --git a/test/data/rsa-privkey-4.der b/test/data/rsa-privkey-4.der index ad64c6304d32d06ba74f9a9234de2cd14a6820ab..c9d0474ce9b4f22a4f32220cad76659ae54b0b98 100644 Binary files a/test/data/rsa-privkey-4.der and b/test/data/rsa-privkey-4.der differ diff --git a/test/data/tahoe-lafs-generated-rsa-privkey.der b/test/data/tahoe-lafs-generated-rsa-privkey.der new file mode 100644 index 0000000000000000000000000000000000000000..ba71aa1212413a9f581dbc52594ea9fee5338ff0 Binary files /dev/null and b/test/data/tahoe-lafs-generated-rsa-privkey.der differ diff --git a/test/expected_values.py b/test/expected_values.py new file mode 100644 index 0000000000000000000000000000000000000000..31d18d9e3a05396116d406689947cfee020a7238 --- /dev/null +++ b/test/expected_values.py @@ -0,0 +1,37 @@ +# Tested on Python 3.9.15 against Tahoe-LAFS bc79cf0a11f06bbdc02a5bb41c6f41fcff727ea5 +# + +from allmydata.crypto import rsa +from allmydata.mutable.common import derive_mutable_keys +from allmydata.util import base32 +from allmydata.util import hashutil + +# Arbitrarily select an IV. +iv = b"\x42" * 16 +# And "peer id" +peerid = b"\x42" * 20 + +with open("data/rsa-privkey-0.der", "rb") as f: + (priv, pub) = rsa.create_signing_keypair_from_string(f.read()) + +writekey, encprivkey, fingerprint = derive_mutable_keys((pub, priv)) +readkey = hashutil.ssk_readkey_hash(writekey) +datakey = hashutil.ssk_readkey_data_hash(iv, readkey) +storage_index = hashutil.ssk_storage_index_hash(readkey) +write_enabler_master = hashutil.ssk_write_enabler_master_hash(writekey) +write_enabler = hashutil.ssk_write_enabler_hash(writekey, peerid) + +print("SDMF") +print("writekey: ", base32.b2a(writekey)) +print("readkey: ", base32.b2a(readkey)) +print("datakey (iv = \x42 * 16): ", base32.b2a(datakey)) +print("storage index: ", base32.b2a(storage_index)) +print("encrypted private key: ", base32.b2a(encprivkey)) +print("signature key hash: ", base32.b2a(fingerprint)) +print("write enabler master: ", base32.b2a(write_enabler_master)) +print("write enabler (peerid = \x42 * 20): ", base32.b2a(write_enabler)) + +(priv, pub) = rsa.create_signing_keypair(2048) +priv_bytes = rsa.der_string_from_signing_key(priv) +with open("data/tahoe-lafs-generated-rsa-privkey.der", "wb") as f: + f.write(priv_bytes)