Ticket #466: 2011-02-p2.diff
File 2011-02-p2.diff, 9.3 KB (added by warner, at 2011-02-07T18:29:36Z) |
---|
-
new file src/allmydata/scripts/admin.py
diff --git a/src/allmydata/scripts/admin.py b/src/allmydata/scripts/admin.py new file mode 100755 index 0000000..d958063
- + 1 2 from twisted.python import usage 3 4 class GenerateKeypairOptions(usage.Options): 5 def getSynopsis(self): 6 return "Usage: tahoe admin generate-keypair" 7 8 def getUsage(self, width=None): 9 t = usage.Options.getUsage(self, width) 10 t += """ 11 Generate an ECDSA192 public/private keypair, dumped to stdout as two lines of 12 base32-encoded text. 13 14 """ 15 return t 16 17 def print_keypair(options): 18 from allmydata.util.keyutil import make_keypair 19 out = options.stdout 20 privkey_vs, pubkey_vs = make_keypair() 21 print >>out, "private:", privkey_vs 22 print >>out, "public:", pubkey_vs 23 24 class DerivePubkeyOptions(usage.Options): 25 def parseArgs(self, privkey): 26 self.privkey = privkey 27 28 def getSynopsis(self): 29 return "Usage: tahoe admin derive-pubkey PRIVKEY" 30 31 def getUsage(self, width=None): 32 t = usage.Options.getUsage(self, width) 33 t += """ 34 Given a private (signing) key that was previously generated with 35 generate-keypair, derive the public key and print it to stdout. 36 37 """ 38 return t 39 40 def derive_pubkey(options): 41 out = options.stdout 42 err = options.stderr 43 from allmydata.util import keyutil 44 privkey_vs = options.privkey 45 sk, pubkey_vs = keyutil.parse_privkey(privkey_vs) 46 print >>out, "private:", privkey_vs 47 print >>out, "public:", pubkey_vs 48 return 0 49 50 class AdminCommand(usage.Options): 51 subCommands = [ 52 ("generate-keypair", None, GenerateKeypairOptions, 53 "Generate a public/private keypair, write to stdout."), 54 ("derive-pubkey", None, DerivePubkeyOptions, 55 "Derive a public key from a private key."), 56 ] 57 def postOptions(self): 58 if not hasattr(self, 'subOptions'): 59 raise usage.UsageError("must specify a subcommand") 60 def getSynopsis(self): 61 return "Usage: tahoe admin SUBCOMMAND" 62 def getUsage(self, width=None): 63 t = usage.Options.getUsage(self, width) 64 t += """ 65 Please run e.g. 'tahoe admin generate-keypair --help' for more details on 66 each subcommand. 67 """ 68 return t 69 70 subDispatch = { 71 "generate-keypair": print_keypair, 72 "derive-pubkey": derive_pubkey, 73 } 74 75 def do_admin(options): 76 so = options.subOptions 77 so.stdout = options.stdout 78 so.stderr = options.stderr 79 f = subDispatch[options.subCommand] 80 return f(so) 81 82 83 subCommands = [ 84 ["admin", None, AdminCommand, "admin subcommands: use 'tahoe admin' for a list"], 85 ] 86 87 dispatch = { 88 "admin": do_admin, 89 } -
src/allmydata/scripts/runner.py
diff --git a/src/allmydata/scripts/runner.py b/src/allmydata/scripts/runner.py index 50f2f07..17c2f7b 100644
a b from cStringIO import StringIO 5 5 from twisted.python import usage 6 6 7 7 from allmydata.scripts.common import BaseOptions 8 from allmydata.scripts import debug, create_node, startstop_node, cli, keygen, stats_gatherer 8 from allmydata.scripts import debug, create_node, startstop_node, cli, keygen, stats_gatherer, admin 9 9 from allmydata.util.encodingutil import quote_output, get_argv_encoding 10 10 11 11 def GROUP(s): … … class Options(BaseOptions, usage.Options): 21 21 + create_node.subCommands 22 22 + keygen.subCommands 23 23 + stats_gatherer.subCommands 24 + admin.subCommands 24 25 + GROUP("Controlling a node") 25 26 + startstop_node.subCommands 26 27 + GROUP("Debugging") … … def runner(argv, 95 96 rc = startstop_node.dispatch[command](so, stdout, stderr) 96 97 elif command in debug.dispatch: 97 98 rc = debug.dispatch[command](so) 99 elif command in admin.dispatch: 100 rc = admin.dispatch[command](so) 98 101 elif command in cli.dispatch: 99 102 rc = cli.dispatch[command](so) 100 103 elif command in ac_dispatch: -
src/allmydata/test/test_cli.py
diff --git a/src/allmydata/test/test_cli.py b/src/allmydata/test/test_cli.py index 46527f7..d655dc4 100644
a b import simplejson 7 7 8 8 from mock import patch 9 9 10 from allmydata.util import fileutil, hashutil, base32 10 from allmydata.util import fileutil, hashutil, base32, keyutil 11 11 from allmydata import uri 12 12 from allmydata.immutable import upload 13 13 from allmydata.dirnode import normalize 14 from allmydata.util import ecdsa 14 15 15 16 # Test that the scripts can be imported. 16 17 from allmydata.scripts import create_node, debug, keygen, startstop_node, \ … … from allmydata.scripts import common 24 25 from allmydata.scripts.common import DEFAULT_ALIAS, get_aliases, get_alias, \ 25 26 DefaultAliasMarker 26 27 27 from allmydata.scripts import cli, debug, runner, backupdb 28 from allmydata.scripts import cli, debug, runner, backupdb, admin 28 29 from allmydata.test.common_util import StallMixin, ReallyEqualMixin 29 30 from allmydata.test.no_network import GridTestMixin 30 31 from twisted.internet import threads # CLI tests use deferToThread … … class Put(GridTestMixin, CLITestMixin, unittest.TestCase): 1012 1013 1013 1014 return d 1014 1015 1016 class Admin(unittest.TestCase): 1017 def do_cli(self, *args, **kwargs): 1018 argv = list(args) 1019 stdin = kwargs.get("stdin", "") 1020 stdout, stderr = StringIO(), StringIO() 1021 d = threads.deferToThread(runner.runner, argv, run_by_human=False, 1022 stdin=StringIO(stdin), 1023 stdout=stdout, stderr=stderr) 1024 def _done(res): 1025 return stdout.getvalue(), stderr.getvalue() 1026 d.addCallback(_done) 1027 return d 1028 1029 def test_generate_keypair(self): 1030 d = self.do_cli("admin", "generate-keypair") 1031 def _done( (stdout, stderr) ): 1032 lines = stdout.split("\n") 1033 privkey_line = lines[0].strip() 1034 pubkey_line = lines[1].strip() 1035 sk_header = "private: priv-v0-" 1036 vk_header = "public: pub-v0-" 1037 self.failUnless(privkey_line.startswith(sk_header), privkey_line) 1038 self.failUnless(pubkey_line.startswith(vk_header), pubkey_line) 1039 privkey_b = base32.a2b(privkey_line[len(sk_header):]) 1040 pubkey_b = base32.a2b(pubkey_line[len(vk_header):]) 1041 sk = ecdsa.SigningKey.from_string(privkey_b) 1042 vk = ecdsa.VerifyingKey.from_string(pubkey_b) 1043 self.failUnlessEqual(sk.get_verifying_key().to_string(), 1044 vk.to_string()) 1045 d.addCallback(_done) 1046 return d 1047 1048 def test_derive_pubkey(self): 1049 priv1,pub1 = keyutil.make_keypair() 1050 d = self.do_cli("admin", "derive-pubkey", priv1) 1051 def _done( (stdout, stderr) ): 1052 lines = stdout.split("\n") 1053 privkey_line = lines[0].strip() 1054 pubkey_line = lines[1].strip() 1055 sk_header = "private: priv-v0-" 1056 vk_header = "public: pub-v0-" 1057 self.failUnless(privkey_line.startswith(sk_header), privkey_line) 1058 self.failUnless(pubkey_line.startswith(vk_header), pubkey_line) 1059 pub2 = pubkey_line[len(vk_header):] 1060 self.failUnlessEqual("pub-v0-"+pub2, pub1) 1061 d.addCallback(_done) 1062 return d 1063 1064 1015 1065 class List(GridTestMixin, CLITestMixin, unittest.TestCase): 1016 1066 def test_list(self): 1017 1067 self.basedir = "cli/List/list" -
new file src/allmydata/util/keyutil.py
diff --git a/src/allmydata/util/keyutil.py b/src/allmydata/util/keyutil.py new file mode 100644 index 0000000..87d3778
- + 1 from allmydata.util.ecdsa import SigningKey, VerifyingKey, NIST192p 2 from allmydata.util import base32 3 4 # in base32, the signing key is 39 chars long, and the verifying key is 77. 5 # in base62, the signing key is 33 chars long, and the verifying key is 65. 6 # in base64, the signing key is 32 chars long, and the verifying key is 64. 7 # 8 # We can't use base64 because we want to reserve punctuation and preserve 9 # cut-and-pasteability. The base62 encoding is not significantly shorter than 10 # the base32 form, and the minor usability improvement is not worth the 11 # documentation/specification confusion of using a non-standard encoding. So 12 # we stick with base32. 13 14 def make_keypair(): 15 privkey = SigningKey.generate(curve=NIST192p) 16 privkey_vs = "priv-v0-%s" % base32.b2a(privkey.to_string()) 17 pubkey = privkey.get_verifying_key() 18 pubkey_vs = "pub-v0-%s" % base32.b2a(pubkey.to_string()) 19 return privkey_vs, pubkey_vs 20 21 def parse_privkey(privkey_vs): 22 if not privkey_vs.startswith("priv-v0-"): 23 raise ValueError("private key must look like 'priv-v0-...', not '%s'" % privkey_vs) 24 privkey_s = privkey_vs[len("priv-v0-"):] 25 sk = SigningKey.from_string(base32.a2b(privkey_s), curve=NIST192p) 26 pubkey = sk.get_verifying_key() 27 pubkey_vs = "pub-v0-%s" % base32.b2a(pubkey.to_string()) 28 return sk, pubkey_vs 29 30 def parse_pubkey(pubkey_vs): 31 if not pubkey_vs.startswith("pub-v0-"): 32 raise ValueError("public key must look like 'pub-v0-...', not '%s'" % pubkey_vs) 33 pubkey_s = pubkey_vs[len("pub-v0-"):] 34 vk = VerifyingKey.from_string(base32.a2b(pubkey_s), curve=NIST192p) 35 return vk