Changeset 99e37df in trunk


Ignore:
Timestamp:
2024-08-08T21:43:51Z (8 months ago)
Author:
meejah <meejah@…>
Branches:
master
Children:
8fb4de0
Parents:
1504bec (diff), ec7185a (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

Merge branch 'master' into remove-future--a-detiste

Files:
5 added
13 edited
1 moved

Legend:

Unmodified
Added
Removed
  • TabularUnified benchmarks/conftest.py

    r1504bec r99e37df  
    102102            needed=number_of_nodes,
    103103            happy=number_of_nodes,
    104             total=number_of_nodes,
     104            total=number_of_nodes + 3,  # Make sure FEC does some work
    105105        )
    106106    )
  • TabularUnified docs/frontends/webapi.rst

    r1504bec r99e37df  
    447447 format, as configured on the Tahoe-LAFS node responding to the request.
    448448
     449 In addition, an optional "private-key=" argument is supported which, if given,
     450 specifies the underlying signing key to be used when creating the directory.
     451 This value must be a DER-encoded 2048-bit RSA private key in urlsafe base64
     452 encoding. (To convert an existing PEM-encoded RSA key file into the format
     453 required, the following commands may be used -- assuming a modern UNIX-like
     454 environment with common tools already installed:
     455 ``openssl rsa -in key.pem -outform der | base64 -w 0 -i - | tr '+/' '-_'``)
     456
     457 Because this key can be used to derive the write capability for the
     458 associated directory, additional care should be taken to ensure that the key is
     459 unique, that it is kept confidential, and that it was derived from an
     460 appropriate (high-entropy) source of randomness. If this argument is omitted
     461 (the default behavior), Tahoe-LAFS will generate an appropriate signing key
     462 using the underlying operating system's source of entropy.
     463
    449464``POST /uri?t=mkdir-with-children``
    450465
     
    454469
    455470 The format of the directory can be controlled with the format= argument in
    456  the query string, as described above.
     471 the query string and a signing key can be specified with the private-key=
     472 argument, as described above.
    457473
    458474 Initial children are provided as the body of the POST form (this is more
  • TabularUnified docs/proposed/index.rst

    r1504bec r99e37df  
    1515
    1616   leasedb
    17    http-storage-node-protocol
  • TabularUnified docs/specifications/index.rst

    r1504bec r99e37df  
    1818   servers-of-happiness
    1919   backends/raic
     20   http-storage-node-protocol
  • TabularUnified integration/test_web.py

    r1504bec r99e37df  
    1313
    1414import time
     15from base64 import urlsafe_b64encode
    1516from urllib.parse import unquote as url_unquote, quote as url_quote
    1617
     18from cryptography.hazmat.primitives.serialization import load_pem_private_key
    1719from twisted.internet.threads import deferToThread
     20from twisted.python.filepath import FilePath
    1821
    1922import allmydata.uri
     23from allmydata.crypto.rsa import (
     24    create_signing_keypair,
     25    der_string_from_signing_key,
     26    PrivateKey,
     27    PublicKey,
     28)
     29from allmydata.mutable.common import derive_mutable_keys
    2030from allmydata.util import jsonbytes as json
    2131
     
    2838
    2939import pytest_twisted
     40
     41
     42DATA_PATH = FilePath(__file__).parent().sibling("src").child("allmydata").child("test").child("data")
     43
    3044
    3145@run_in_thread
     
    542556    cap = allmydata.uri.from_string(resp)
    543557    assert isinstance(cap, allmydata.uri.DirectoryURI)
     558
     559
     560@run_in_thread
     561def test_mkdir_with_random_private_key(alice):
     562    """
     563    Create a new directory with ?t=mkdir&private-key=... using a
     564    randomly-generated RSA private key.
     565
     566    The writekey and fingerprint derived from the provided RSA key
     567    should match those of the newly-created directory capability.
     568    """
     569
     570    privkey, pubkey = create_signing_keypair(2048)
     571
     572    writekey, _, fingerprint = derive_mutable_keys((pubkey, privkey))
     573
     574    # The "private-key" parameter takes a DER-encoded RSA private key
     575    # encoded in URL-safe base64; PEM blocks are not supported.
     576    privkey_der = der_string_from_signing_key(privkey)
     577    privkey_encoded = urlsafe_b64encode(privkey_der).decode("ascii")
     578
     579    resp = util.web_post(
     580        alice.process, u"uri",
     581        params={
     582            u"t": "mkdir",
     583            u"private-key": privkey_encoded,
     584        },
     585    )
     586    assert resp.startswith(b"URI:DIR2")
     587
     588    dircap = allmydata.uri.from_string(resp)
     589    assert isinstance(dircap, allmydata.uri.DirectoryURI)
     590
     591    # DirectoryURI objects lack 'writekey' and 'fingerprint' attributes
     592    # so extract them from the enclosed WriteableSSKFileURI object.
     593    filecap = dircap.get_filenode_cap()
     594    assert isinstance(filecap, allmydata.uri.WriteableSSKFileURI)
     595
     596    assert (writekey, fingerprint) == (filecap.writekey, filecap.fingerprint)
     597
     598
     599@run_in_thread
     600def test_mkdir_with_known_private_key(alice):
     601    """
     602    Create a new directory with ?t=mkdir&private-key=... using a
     603    known-in-advance RSA private key.
     604
     605    The writekey and fingerprint derived from the provided RSA key
     606    should match those of the newly-created directory capability.
     607    In addition, because the writekey and fingerprint are derived
     608    deterministically, given the same RSA private key, the resultant
     609    directory capability should always be the same.
     610    """
     611    # Generated with `openssl genrsa -out openssl-rsa-2048-3.txt 2048`
     612    pempath = DATA_PATH.child("openssl-rsa-2048-3.txt")
     613    privkey = load_pem_private_key(pempath.getContent(), password=None)
     614    assert isinstance(privkey, PrivateKey)
     615    pubkey = privkey.public_key()
     616    assert isinstance(pubkey, PublicKey)
     617
     618    writekey, _, fingerprint = derive_mutable_keys((pubkey, privkey))
     619
     620    # The "private-key" parameter takes a DER-encoded RSA private key
     621    # encoded in URL-safe base64; PEM blocks are not supported.
     622    privkey_der = der_string_from_signing_key(privkey)
     623    privkey_encoded = urlsafe_b64encode(privkey_der).decode("ascii")
     624
     625    resp = util.web_post(
     626        alice.process, u"uri",
     627        params={
     628            u"t": "mkdir",
     629            u"private-key": privkey_encoded,
     630        },
     631    )
     632    assert resp.startswith(b"URI:DIR2")
     633
     634    dircap = allmydata.uri.from_string(resp)
     635    assert isinstance(dircap, allmydata.uri.DirectoryURI)
     636
     637    # DirectoryURI objects lack 'writekey' and 'fingerprint' attributes
     638    # so extract them from the enclosed WriteableSSKFileURI object.
     639    filecap = dircap.get_filenode_cap()
     640    assert isinstance(filecap, allmydata.uri.WriteableSSKFileURI)
     641
     642    assert (writekey, fingerprint) == (filecap.writekey, filecap.fingerprint)
     643
     644    assert resp == b"URI:DIR2:3oo7j7f7qqxnet2z2lf57ucup4:cpktmsxlqnd5yeekytxjxvff5e6d6fv7py6rftugcndvss7tzd2a"
     645
     646
     647@run_in_thread
     648def test_mkdir_with_children_and_random_private_key(alice):
     649    """
     650    Create a new directory with ?t=mkdir-with-children&private-key=...
     651    using a randomly-generated RSA private key.
     652
     653    The writekey and fingerprint derived from the provided RSA key
     654    should match those of the newly-created directory capability.
     655    """
     656
     657    # create a file to put in our directory
     658    FILE_CONTENTS = u"some file contents\n" * 500
     659    resp = requests.put(
     660        util.node_url(alice.process.node_dir, u"uri"),
     661        data=FILE_CONTENTS,
     662    )
     663    filecap = resp.content.strip()
     664
     665    # create a (sub) directory to put in our directory
     666    resp = requests.post(
     667        util.node_url(alice.process.node_dir, u"uri"),
     668        params={
     669            u"t": u"mkdir",
     670        }
     671    )
     672    # (we need both the read-write and read-only URIs I guess)
     673    dircap = resp.content
     674    dircap_obj = allmydata.uri.from_string(dircap)
     675    dircap_ro = dircap_obj.get_readonly().to_string()
     676
     677    # create json information about our directory
     678    meta = {
     679        "a_file": [
     680            "filenode", {
     681                "ro_uri": filecap,
     682                "metadata": {
     683                    "ctime": 1202777696.7564139,
     684                    "mtime": 1202777696.7564139,
     685                    "tahoe": {
     686                        "linkcrtime": 1202777696.7564139,
     687                        "linkmotime": 1202777696.7564139
     688                    }
     689                }
     690            }
     691        ],
     692        "some_subdir": [
     693            "dirnode", {
     694                "rw_uri": dircap,
     695                "ro_uri": dircap_ro,
     696                "metadata": {
     697                    "ctime": 1202778102.7589991,
     698                    "mtime": 1202778111.2160511,
     699                    "tahoe": {
     700                        "linkcrtime": 1202777696.7564139,
     701                        "linkmotime": 1202777696.7564139
     702                    }
     703                }
     704            }
     705        ]
     706    }
     707
     708    privkey, pubkey = create_signing_keypair(2048)
     709
     710    writekey, _, fingerprint = derive_mutable_keys((pubkey, privkey))
     711
     712    # The "private-key" parameter takes a DER-encoded RSA private key
     713    # encoded in URL-safe base64; PEM blocks are not supported.
     714    privkey_der = der_string_from_signing_key(privkey)
     715    privkey_encoded = urlsafe_b64encode(privkey_der).decode("ascii")
     716
     717    # create a new directory with one file and one sub-dir (all-at-once)
     718    # with the supplied RSA private key
     719    resp = util.web_post(
     720        alice.process, u"uri",
     721        params={
     722            u"t": "mkdir-with-children",
     723            u"private-key": privkey_encoded,
     724        },
     725        data=json.dumps(meta),
     726    )
     727    assert resp.startswith(b"URI:DIR2")
     728
     729    dircap = allmydata.uri.from_string(resp)
     730    assert isinstance(dircap, allmydata.uri.DirectoryURI)
     731
     732    # DirectoryURI objects lack 'writekey' and 'fingerprint' attributes
     733    # so extract them from the enclosed WriteableSSKFileURI object.
     734    filecap = dircap.get_filenode_cap()
     735    assert isinstance(filecap, allmydata.uri.WriteableSSKFileURI)
     736
     737    assert (writekey, fingerprint) == (filecap.writekey, filecap.fingerprint)
     738
     739
     740@run_in_thread
     741def test_mkdir_with_children_and_known_private_key(alice):
     742    """
     743    Create a new directory with ?t=mkdir-with-children&private-key=...
     744    using a known-in-advance RSA private key.
     745
     746
     747    The writekey and fingerprint derived from the provided RSA key
     748    should match those of the newly-created directory capability.
     749    In addition, because the writekey and fingerprint are derived
     750    deterministically, given the same RSA private key, the resultant
     751    directory capability should always be the same.
     752    """
     753
     754    # create a file to put in our directory
     755    FILE_CONTENTS = u"some file contents\n" * 500
     756    resp = requests.put(
     757        util.node_url(alice.process.node_dir, u"uri"),
     758        data=FILE_CONTENTS,
     759    )
     760    filecap = resp.content.strip()
     761
     762    # create a (sub) directory to put in our directory
     763    resp = requests.post(
     764        util.node_url(alice.process.node_dir, u"uri"),
     765        params={
     766            u"t": u"mkdir",
     767        }
     768    )
     769    # (we need both the read-write and read-only URIs I guess)
     770    dircap = resp.content
     771    dircap_obj = allmydata.uri.from_string(dircap)
     772    dircap_ro = dircap_obj.get_readonly().to_string()
     773
     774    # create json information about our directory
     775    meta = {
     776        "a_file": [
     777            "filenode", {
     778                "ro_uri": filecap,
     779                "metadata": {
     780                    "ctime": 1202777696.7564139,
     781                    "mtime": 1202777696.7564139,
     782                    "tahoe": {
     783                        "linkcrtime": 1202777696.7564139,
     784                        "linkmotime": 1202777696.7564139
     785                    }
     786                }
     787            }
     788        ],
     789        "some_subdir": [
     790            "dirnode", {
     791                "rw_uri": dircap,
     792                "ro_uri": dircap_ro,
     793                "metadata": {
     794                    "ctime": 1202778102.7589991,
     795                    "mtime": 1202778111.2160511,
     796                    "tahoe": {
     797                        "linkcrtime": 1202777696.7564139,
     798                        "linkmotime": 1202777696.7564139
     799                    }
     800                }
     801            }
     802        ]
     803    }
     804
     805    # Generated with `openssl genrsa -out openssl-rsa-2048-4.txt 2048`
     806    pempath = DATA_PATH.child("openssl-rsa-2048-4.txt")
     807    privkey = load_pem_private_key(pempath.getContent(), password=None)
     808    assert isinstance(privkey, PrivateKey)
     809    pubkey = privkey.public_key()
     810    assert isinstance(pubkey, PublicKey)
     811
     812    writekey, _, fingerprint = derive_mutable_keys((pubkey, privkey))
     813
     814    # The "private-key" parameter takes a DER-encoded RSA private key
     815    # encoded in URL-safe base64; PEM blocks are not supported.
     816    privkey_der = der_string_from_signing_key(privkey)
     817    privkey_encoded = urlsafe_b64encode(privkey_der).decode("ascii")
     818
     819    # create a new directory with one file and one sub-dir (all-at-once)
     820    # with the supplied RSA private key
     821    resp = util.web_post(
     822        alice.process, u"uri",
     823        params={
     824            u"t": "mkdir-with-children",
     825            u"private-key": privkey_encoded,
     826        },
     827        data=json.dumps(meta),
     828    )
     829    assert resp.startswith(b"URI:DIR2")
     830
     831    dircap = allmydata.uri.from_string(resp)
     832    assert isinstance(dircap, allmydata.uri.DirectoryURI)
     833
     834    # DirectoryURI objects lack 'writekey' and 'fingerprint' attributes
     835    # so extract them from the enclosed WriteableSSKFileURI object.
     836    filecap = dircap.get_filenode_cap()
     837    assert isinstance(filecap, allmydata.uri.WriteableSSKFileURI)
     838
     839    assert (writekey, fingerprint) == (filecap.writekey, filecap.fingerprint)
     840
     841    assert resp == b"URI:DIR2:ppwzpwrd37xi7tpribxyaa25uy:imdws47wwpzfkc5vfllo4ugspb36iit4cqps6ttuhaouc66jb2da"
  • TabularUnified src/allmydata/client.py

    r1504bec r99e37df  
    3333from allmydata.crypto import rsa, ed25519
    3434from allmydata.crypto.util import remove_prefix
     35from allmydata.dirnode import DirectoryNode
    3536from allmydata.storage.server import StorageServer, FoolscapStorageServer
    3637from allmydata import storage_client
     
    11261127        return self.nodemaker.create_from_cap(write_uri, read_uri, deep_immutable=deep_immutable, name=name)
    11271128
    1128     def create_dirnode(self, initial_children=None, version=None):
    1129         d = self.nodemaker.create_new_mutable_directory(initial_children, version=version)
     1129    def create_dirnode(
     1130        self,
     1131        initial_children: dict | None = None,
     1132        version: int | None = None,
     1133        *,
     1134        unique_keypair: tuple[rsa.PublicKey, rsa.PrivateKey] | None = None
     1135    ) -> DirectoryNode:
     1136        """
     1137        Create a new directory.
     1138
     1139        :param initial_children: If given, a structured dict representing the
     1140            initial content of the created directory. See
     1141            `docs/frontends/webapi.rst` for examples.
     1142
     1143        :param version: If given, an int representing the mutable file format
     1144            of the new object. Acceptable values are currently `SDMF_VERSION`
     1145            or `MDMF_VERSION` (corresponding to 0 or 1, respectively, as
     1146            defined in `allmydata.interfaces`). If no such value is provided,
     1147            the default mutable format will be used (currently SDMF).
     1148
     1149        :param unique_keypair: an optional tuple containing the RSA public
     1150            and private key to be used for the new directory. Typically, this
     1151            value is omitted (in which case a new random keypair will be
     1152            generated at creation time).
     1153
     1154            **Warning** This value independently determines the identity of
     1155            the mutable object to create.  There cannot be two different
     1156            mutable objects that share a keypair.  They will merge into one
     1157            object (with undefined contents).
     1158
     1159        :return: A Deferred which will fire with a representation of the new
     1160            directory after it has been created.
     1161        """
     1162        d = self.nodemaker.create_new_mutable_directory(
     1163            initial_children,
     1164            version=version,
     1165            keypair=unique_keypair,
     1166        )
    11301167        return d
    11311168
  • TabularUnified src/allmydata/crypto/aes.py

    r1504bec r99e37df  
    7878
    7979    _validate_cryptor(encryptor, encrypt=True)
    80     if not isinstance(plaintext, bytes):
    81         raise ValueError('Plaintext must be bytes')
     80    if not isinstance(plaintext, (bytes, memoryview)):
     81        raise ValueError(f'Plaintext must be bytes or memoryview: {type(plaintext)}')
    8282
    8383    return encryptor.update(plaintext)
     
    117117
    118118    _validate_cryptor(decryptor, encrypt=False)
    119     if not isinstance(plaintext, bytes):
    120         raise ValueError('Plaintext must be bytes')
     119    if not isinstance(plaintext, (bytes, memoryview)):
     120        raise ValueError(f'Plaintext must be bytes or memoryview: {type(plaintext)}')
    121121
    122122    return decryptor.update(plaintext)
  • TabularUnified src/allmydata/immutable/downloader/node.py

    r1504bec r99e37df  
    412412    def process_blocks(self, segnum, blocks):
    413413        start = now()
    414         d = defer.maybeDeferred(self._decode_blocks, segnum, blocks)
     414        d = self._decode_blocks(segnum, blocks)
    415415        d.addCallback(self._check_ciphertext_hash, segnum)
    416416        def _deliver(result):
  • TabularUnified src/allmydata/mutable/filenode.py

    r1504bec r99e37df  
    1515from allmydata.util import hashutil, log, consumer, deferredutil, mathutil
    1616from allmydata.util.assertutil import precondition
     17from allmydata.util.cputhreadpool import defer_to_thread
    1718from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI, \
    1819                          WriteableMDMFFileURI, ReadonlyMDMFFileURI
     
    129130        return self
    130131
    131     def create_with_keys(self, keypair, contents,
     132    @deferredutil.async_to_deferred
     133    async def create_with_keys(self, keypair, contents,
    132134                         version=SDMF_VERSION):
    133135        """Call this to create a brand-new mutable file. It will create the
     
    138140        """
    139141        self._pubkey, self._privkey = keypair
    140         self._writekey, self._encprivkey, self._fingerprint = derive_mutable_keys(
    141             keypair,
     142        self._writekey, self._encprivkey, self._fingerprint = await defer_to_thread(
     143            derive_mutable_keys, keypair
    142144        )
    143145        if version == MDMF_VERSION:
     
    150152        self._storage_index = self._uri.storage_index
    151153        initial_contents = self._get_initial_contents(contents)
    152         return self._upload(initial_contents, None)
     154        return await self._upload(initial_contents, None)
    153155
    154156    def _get_initial_contents(self, contents):
  • TabularUnified src/allmydata/mutable/retrieve.py

    r1504bec r99e37df  
    55
    66import time
    7 
    87from itertools import count
     8
    99from zope.interface import implementer
    1010from twisted.internet import defer
     
    874874        self.log("decoding segment %d" % segnum)
    875875        if segnum == self._num_segments - 1:
    876             d = defer.maybeDeferred(self._tail_decoder.decode, shares, shareids)
    877         else:
    878             d = defer.maybeDeferred(self._segment_decoder.decode, shares, shareids)
    879         def _process(buffers):
    880             segment = b"".join(buffers)
     876            d = self._tail_decoder.decode(shares, shareids)
     877        else:
     878            d = self._segment_decoder.decode(shares, shareids)
     879
     880        # For larger shares, this can take a few milliseconds. As such, we want
     881        # to unblock the event loop. In newer Python b"".join() will release
     882        # the GIL: https://github.com/python/cpython/issues/80232
     883        @deferredutil.async_to_deferred
     884        async def _got_buffers(buffers):
     885            return await defer_to_thread(lambda: b"".join(buffers))
     886
     887        d.addCallback(_got_buffers)
     888
     889        def _process(segment):
    881890            self.log(format="now decoding segment %(segnum)s of %(numsegs)s",
    882891                     segnum=segnum,
     
    929938        )
    930939
    931 
    932     def _try_to_validate_privkey(self, enc_privkey, reader, server):
     940    @deferredutil.async_to_deferred
     941    async def _try_to_validate_privkey(self, enc_privkey, reader, server):
    933942        node_writekey = self._node.get_writekey()
    934         alleged_privkey_s = decrypt_privkey(node_writekey, enc_privkey)
    935         alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
    936         if alleged_writekey != node_writekey:
     943
     944        def get_privkey():
     945            alleged_privkey_s = decrypt_privkey(node_writekey, enc_privkey)
     946            alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
     947            if alleged_writekey != node_writekey:
     948                return None
     949            privkey, _ = rsa.create_signing_keypair_from_string(alleged_privkey_s)
     950            return privkey
     951
     952        privkey = await defer_to_thread(get_privkey)
     953        if privkey is None:
    937954            self.log("invalid privkey from %s shnum %d" %
    938955                     (reader, reader.shnum),
     
    951968        self.log("got valid privkey from shnum %d on reader %s" %
    952969                 (reader.shnum, reader))
    953         privkey, _ = rsa.create_signing_keypair_from_string(alleged_privkey_s)
    954970        self._node._populate_encprivkey(enc_privkey)
    955971        self._node._populate_privkey(privkey)
  • TabularUnified src/allmydata/nodemaker.py

    r1504bec r99e37df  
    136136        return d
    137137
    138     def create_new_mutable_directory(self, initial_children=None, version=None):
     138    def create_new_mutable_directory(
     139        self,
     140        initial_children=None,
     141        version=None,
     142        *,
     143        keypair: tuple[PublicKey, PrivateKey] | None = None,
     144    ):
    139145        if initial_children is None:
    140146            initial_children = {}
     
    146152                                     MutableData(pack_children(initial_children,
    147153                                                    n.get_writekey())),
    148                                      version=version)
     154                                     version=version,
     155                                     keypair=keypair)
    149156        d.addCallback(self._create_dirnode)
    150157        return d
  • TabularUnified src/allmydata/test/test_dirnode.py

    r1504bec r99e37df  
    1010from twisted.internet import defer
    1111from twisted.internet.interfaces import IConsumer
     12from twisted.python.filepath import FilePath
    1213from allmydata import uri, dirnode
    1314from allmydata.client import _Client
     15from allmydata.crypto.rsa import create_signing_keypair
    1416from allmydata.immutable import upload
    1517from allmydata.immutable.literal import LiteralFileNode
     
    2022     MDMF_VERSION, SDMF_VERSION
    2123from allmydata.mutable.filenode import MutableFileNode
    22 from allmydata.mutable.common import UncoordinatedWriteError
     24from allmydata.mutable.common import (
     25    UncoordinatedWriteError,
     26    derive_mutable_keys,
     27)
    2328from allmydata.util import hashutil, base32
    2429from allmydata.util.netstring import split_netstring
     
    2631from allmydata.test.common import make_chk_file_uri, make_mutable_file_uri, \
    2732     ErrorMixin
     33from allmydata.test.mutable.util import (
     34    FakeStorage,
     35    make_nodemaker_with_peers,
     36    make_peer,
     37)
    2838from allmydata.test.no_network import GridTestMixin
    2939from allmydata.unknown import UnknownNode, strip_prefix_for_ro
    3040from allmydata.nodemaker import NodeMaker
    3141from base64 import b32decode
     42from cryptography.hazmat.primitives.serialization import load_pem_private_key
    3243import allmydata.test.common_util as testutil
    3344
     
    19791990        d.addCallback(_test_adder)
    19801991        return d
     1992
     1993
     1994class DeterministicDirnode(testutil.ReallyEqualMixin, testutil.ShouldFailMixin, unittest.TestCase):
     1995    def setUp(self):
     1996        # Copied from allmydata.test.mutable.test_filenode
     1997        super(DeterministicDirnode, self).setUp()
     1998        self._storage = FakeStorage()
     1999        self._peers = list(
     2000            make_peer(self._storage, n)
     2001            for n
     2002            in range(10)
     2003        )
     2004        self.nodemaker = make_nodemaker_with_peers(self._peers)
     2005
     2006    async def test_create_with_random_keypair(self):
     2007        """
     2008        Create a dirnode using a random RSA keypair.
     2009
     2010        The writekey and fingerprint of the enclosed mutable filecap
     2011        should match those derived from the given keypair.
     2012        """
     2013        privkey, pubkey = create_signing_keypair(2048)
     2014        writekey, _, fingerprint = derive_mutable_keys((pubkey, privkey))
     2015
     2016        node = await self.nodemaker.create_new_mutable_directory(
     2017            keypair=(pubkey, privkey)
     2018        )
     2019        self.failUnless(isinstance(node, dirnode.DirectoryNode))
     2020
     2021        dircap = uri.from_string(node.get_uri())
     2022        self.failUnless(isinstance(dircap, uri.DirectoryURI))
     2023
     2024        filecap = dircap.get_filenode_cap()
     2025        self.failUnless(isinstance(filecap, uri.WriteableSSKFileURI))
     2026
     2027        self.failUnlessReallyEqual(filecap.writekey, writekey)
     2028        self.failUnlessReallyEqual(filecap.fingerprint, fingerprint)
     2029
     2030    async def test_create_with_known_keypair(self):
     2031        """
     2032        Create a dirnode using a known RSA keypair.
     2033
     2034        The writekey and fingerprint of the enclosed mutable filecap
     2035        should match those derived from the given keypair. Because
     2036        these values are derived deterministically, given the same
     2037        keypair, the resulting filecap should also always be the same.
     2038        """
     2039        # Generated with `openssl genrsa -out openssl-rsa-2048-2.txt 2048`
     2040        pempath = FilePath(__file__).sibling("data").child("openssl-rsa-2048-2.txt")
     2041        privkey = load_pem_private_key(pempath.getContent(), password=None)
     2042        pubkey = privkey.public_key()
     2043        writekey, _, fingerprint = derive_mutable_keys((pubkey, privkey))
     2044
     2045        node = await self.nodemaker.create_new_mutable_directory(
     2046            keypair=(pubkey, privkey)
     2047        )
     2048        self.failUnless(isinstance(node, dirnode.DirectoryNode))
     2049
     2050        dircap = uri.from_string(node.get_uri())
     2051        self.failUnless(isinstance(dircap, uri.DirectoryURI))
     2052
     2053        filecap = dircap.get_filenode_cap()
     2054        self.failUnless(isinstance(filecap, uri.WriteableSSKFileURI))
     2055
     2056        self.failUnlessReallyEqual(filecap.writekey, writekey)
     2057        self.failUnlessReallyEqual(filecap.fingerprint, fingerprint)
     2058
     2059        self.failUnlessReallyEqual(
     2060            # Despite being named "to_string", this actually returns bytes..
     2061            dircap.to_string(),
     2062            b'URI:DIR2:n4opqgewgcn4mddu4oiippaxru:ukpe4z6xdlujdpguoabergyih3bj7iaafukdqzwthy2ytdd5bs2a'
     2063        )
  • TabularUnified src/allmydata/web/unlinked.py

    r1504bec r99e37df  
    161161    if file_format:
    162162        mt = get_mutable_type(file_format)
    163     d = client.create_dirnode(version=mt)
     163    d = client.create_dirnode(version=mt, unique_keypair=get_keypair(req))
    164164    redirect = get_arg(req, "redirect_to_result", "false")
    165165    if boolean_of_arg(redirect):
     
    179179    kids_json = req.content.read()
    180180    kids = convert_children_json(client.nodemaker, kids_json)
    181     d = client.create_dirnode(initial_children=kids)
     181    d = client.create_dirnode(initial_children=kids, unique_keypair=get_keypair(req))
    182182    redirect = get_arg(req, "redirect_to_result", "false")
    183183    if boolean_of_arg(redirect):
Note: See TracChangeset for help on using the changeset viewer.