Browse Source

initialize repository

pull/1/head
libre-net-society 8 years ago
commit
03af5634c1
  1. 4
      .gitignore
  2. 70
      README.md
  3. 2
      data/.gitignore
  4. 1
      output/.gitignore
  5. 125
      pyseeder.py
  6. 0
      pyseeder/__init__.py
  7. 82
      pyseeder/crypto.py
  8. 84
      pyseeder/su3file.py
  9. 58
      pyseeder/transport.py
  10. 0
      pyseeder/transports/__init__.py
  11. 3
      pyseeder/transports/dropbox.py
  12. 39
      pyseeder/transports/git.py
  13. 7
      pyseeder/utils.py
  14. 6
      requirements.txt
  15. 10
      transports.ini.example

4
.gitignore vendored

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
__pycache__
*.pyc
venv/
transports.ini

70
README.md

@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
pyseeder
========
Reseed data managment tools for I2P
* Generate reseed signing keypair
* Make reseed data files (su3)
* Download su3 files from official servers for mirroring
* Upload reseed data to different places (with plugins)
Reseed transports are implemented so that users can bootstrap their I2P nodes
without needing to connect to "official" I2P reseeds. This makes I2P more
invisible for firewalls.
Install requirements
--------------------
# apt-get install python3 python3-pip
# pip3 install -r requirements.txt
Usage
-----
$ python3 pyseeder.py --help
$ python3 pyseeder.py keygen --help
Generating keypair
------------------
$ pyhon3 pyseeder.py keygen --cert data/user_at_mail.i2p.crt --private-key data/priv_key.pem --signer-id user@mail.i2p
This will generate certificate (user\_at\_mail.i2p.crt) and private RSA key
(priv\_key.pem) in data folder. E-mail is used as certificate identifier.
Script will prompt for private key password.
Generating reseed data
----------------------
$ YOUR_PASSWORD="Pa55w0rd"
$ echo $YOUR_PASSWORD | python3 pyseeder.py reseed --netdb /path/to/netDb --private-key data/priv_key.pem --outfile output/i2pseeds.su3 --signer-id user@mail.i2p
This will generate file i2pseeds.su3 in output folder, using user@mail.i2p as
certificate identifier.
Note: you'll have to enter your private key password to stdin, the above
is one of the ways to do it (for cron and scripts).
Download su3 file from official servers
---------------------------------------
$ python3 pyseeder.py transport.pull --urls https://reseed.i2p-projekt.de/ https://reseed.i2p.vzaws.com:8443/ --outfile output/i2pseeds.su3
Note: --urls parameter is optional, defaults are "official" I2P reseeds.
Upload su3 file with pluggable transports
-----------------------------------------
$ python3 pyseeder.py transport.push --config transports.ini --file output/i2pseeds.su3
All parameters are optional. Copy file transports.ini.example to
transports.ini. Edit your settings in this new file.

2
data/.gitignore vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
*.crt
*.pem

1
output/.gitignore vendored

@ -0,0 +1 @@ @@ -0,0 +1 @@
*.su3

125
pyseeder.py

@ -0,0 +1,125 @@ @@ -0,0 +1,125 @@
#! /usr/bin/env python3
import os, os.path
import sys
from getpass import getpass
import argparse
from pyseeder.crypto import keygen
from pyseeder.su3file import SU3File
import pyseeder.transport
from pyseeder.utils import PyseederException
def keygen_action(args):
"""Sub-command to generate keys"""
priv_key_password = getpass("Set private key password: ").encode("utf-8")
keygen(args.cert, args.private_key, priv_key_password, args.signer_id)
def reseed_action(args):
"""Sub-command to generate reseed file"""
priv_key_password = input().encode("utf-8")
su3file = SU3File(args.signer_id)
su3file.reseed(args.netdb)
su3file.write(args.outfile, args.private_key, priv_key_password)
def transport_pull_action(args):
"""Sub-command for downloading su3 file"""
import random
random.shuffle(args.urls)
for u in args.urls:
if pyseeder.transport.download(u, args.outfile):
return True
raise PyseederException("Failed to download su3 file")
def transport_push_action(args):
"""Sub-command for uploading su3 file with transports"""
if not os.path.isfile(args.config):
raise PyseederException("Can't read transports config file")
import configparser
config = configparser.ConfigParser()
config.read(args.config)
pyseeder.transport.upload(args.file, config)
def main():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(title="actions",
help="Command to execute")
kg_parser = subparsers.add_parser(
"keygen",
description="Generates keypair for your reseed",
usage="""
%(prog)s --cert data/user_at_mail.i2p.crt \\
--private-key data/priv_key.pem --signer-id user@mail.i2p"""
)
kg_parser.add_argument("--signer-id", required=True,
help="Identifier of certificate (example: user@mail.i2p)")
kg_parser.add_argument("--private-key", default="data/priv_key.pem",
help="RSA private key (default: data/priv_key.pem)")
kg_parser.add_argument("--cert", required=True,
help="Certificate (example: output/user_at_mail.i2p.crt)")
kg_parser.set_defaults(func=keygen_action)
rs_parser = subparsers.add_parser(
"reseed",
description="Creates su3 reseed file",
usage="""
echo $YOUR_PASSWORD | %(prog)s --netdb /path/to/netDb \\
--private-key data/priv_key.pem --outfile output/i2pseeds.su3 \\
--signer-id user@mail.i2p"""
)
rs_parser.add_argument("--signer-id", required=True,
help="Identifier of certificate (example: user@mail.i2p)")
rs_parser.add_argument("--private-key", default="data/priv_key.pem",
help="RSA private key (default: data/priv_key.pem)")
rs_parser.add_argument("-o", "--outfile", default="output/i2pseeds.su3",
help="Output file (default: output/i2pseeds.su3)")
rs_parser.add_argument("--netdb", required=True,
help="Path to netDb folder (example: ~/.i2pd/netDb)")
rs_parser.set_defaults(func=reseed_action)
tpull_parser = subparsers.add_parser(
"transport.pull",
description="Download su3 file from random reseed server",
usage="""
%(prog)s --urls https://reseed.i2p-projekt.de/ \\
https://reseed.i2p.vzaws.com:8443/ \\
--outfile output/i2pseeds.su3"""
)
tpull_parser.add_argument("--urls", default=pyseeder.transport.RESEED_URLS,
nargs="*", help="""Reseed URLs separated by space, default are
mainline I2P (like https://reseed.i2p-projekt.de/)""")
tpull_parser.add_argument("-o", "--outfile", default="output/i2pseeds.su3",
help="Output file (default: output/i2pseeds.su3)")
tpull_parser.set_defaults(func=transport_pull_action)
tpush_parser = subparsers.add_parser(
"transport.push",
description="Upload su3 file with transports",
usage="%(prog)s --config transports.ini --file output/i2pseeds.su3"
)
tpush_parser.add_argument("--config", default="transports.ini",
help="Transports config file (default: transports.ini)")
tpush_parser.add_argument("-f", "--file", default="output/i2pseeds.su3",
help=".su3 file (default: output/i2pseeds.su3)")
tpush_parser.set_defaults(func=transport_push_action)
args = parser.parse_args()
if hasattr(args, "func"):
try:
args.func(args)
except PyseederException as pe:
print("Pyseeder error: {}".format(pe))
sys.exit(1)
else:
parser.print_help()
if __name__ == "__main__":
main()

0
pyseeder/__init__.py

82
pyseeder/crypto.py

@ -0,0 +1,82 @@ @@ -0,0 +1,82 @@
import os, os.path
import random
import sys
import datetime
from pyseeder.utils import PyseederException
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
def keygen(pub_key, priv_key, priv_key_password, user_id):
"""Generate new private key and certificate RSA_SHA512_4096"""
for f in [pub_key, priv_key]:
if not os.access(os.path.dirname(f) or ".", os.W_OK):
raise PyseederException("Can't write {}, access forbidden").format(f)
# Generate our key
key = rsa.generate_private_key(public_exponent=65537, key_size=4096,
backend=default_backend())
# Write our key to disk for safe keeping
with open(priv_key, "wb") as f:
f.write(key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.BestAvailableEncryption(
priv_key_password),
))
# Various details about who we are. For a self-signed certificate the
# subject and issuer are always the same.
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "XX"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "XX"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "XX"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "I2P Anonymous Network"),
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "I2P"),
x509.NameAttribute(NameOID.COMMON_NAME, user_id),
])
cert = x509.CertificateBuilder() \
.subject_name(subject) \
.issuer_name(issuer) \
.public_key(key.public_key()) \
.not_valid_before(datetime.datetime.utcnow()) \
.not_valid_after(
datetime.datetime.utcnow() + datetime.timedelta(days=365*10)
) \
.serial_number(random.randrange(1000000000, 2000000000)) \
.add_extension(
x509.SubjectKeyIdentifier.from_public_key(key.public_key()),
critical=False,
).sign(key, hashes.SHA512(), default_backend())
with open(pub_key, "wb") as f:
f.write(cert.public_bytes(serialization.Encoding.PEM))
def append_signature(target_file, priv_key, priv_key_password):
"""Append signature to the end of file"""
if not os.path.exists(priv_key):
raise PyseederException("Wrong private key path")
if not os.access(priv_key, os.R_OK):
raise PyseederException("Can't read private key, access forbidden")
with open(target_file, "rb") as f:
contents = f.read()
with open(priv_key, "rb") as kf:
private_key = serialization.load_pem_private_key(
kf.read(), password=priv_key_password, backend=default_backend())
signature = private_key.sign(contents, padding.PKCS1v15(), hashes.SHA512())
with open(target_file, "ab") as f:
f.write(signature)

84
pyseeder/su3file.py

@ -0,0 +1,84 @@ @@ -0,0 +1,84 @@
import os, os.path
import time, datetime
import random
import io
from zipfile import ZipFile, ZIP_DEFLATED
import pyseeder.crypto
from pyseeder.utils import PyseederException
class SU3File:
"""SU3 file format"""
def __init__(self, signer_id):
self.SIGNER_ID = signer_id
self.SIGNER_ID_LENGTH = len(self.SIGNER_ID)
self.SIGNATURE_TYPE = 0x0006
self.SIGNATURE_LENGTH = 512
self.VERSION_LENGTH = 0x10
self.FILE_TYPE = None
self.CONTENT_TYPE = None
self.CONTENT = None
self.CONTENT_LENGTH = None
self.VERSION = str(int(time.time())).encode("utf-8")
#self.keytype = "RSA_SHA512_4096"
def write(self, filename, priv_key, priv_key_password):
"""Write file to disc"""
if not os.access(os.path.dirname(filename) or ".", os.W_OK):
raise PyseederException("Can't write su3 file, access forbidden")
nullbyte = bytes([0])
with open(filename, "wb") as f:
f.write("I2Psu3".encode("utf-8"))
f.write(bytes([0,0]))
f.write(self.SIGNATURE_TYPE.to_bytes(2, "big"))
f.write(self.SIGNATURE_LENGTH.to_bytes(2, "big"))
f.write(nullbyte)
f.write(bytes([self.VERSION_LENGTH]))
f.write(nullbyte)
f.write(bytes([self.SIGNER_ID_LENGTH]))
f.write(self.CONTENT_LENGTH.to_bytes(8, "big"))
f.write(nullbyte)
f.write(bytes([self.FILE_TYPE]))
f.write(nullbyte)
f.write(bytes([self.CONTENT_TYPE]))
f.write(bytes([0 for _ in range(12)]))
f.write(self.VERSION + bytes(
[0 for _ in range(16 - len(self.VERSION))]))
f.write(self.SIGNER_ID.encode("utf-8"))
f.write(self.CONTENT)
pyseeder.crypto.append_signature(filename, priv_key, priv_key_password)
def reseed(self, netdb):
"""Compress netdb entries and set content"""
zip_file = io.BytesIO()
dat_files = []
if not os.path.exists(netdb):
raise PyseederException("Wrong netDb path")
if not os.access(netdb, os.R_OK):
raise PyseederException("Can't read netDb, access forbidden")
for root, dirs, files in os.walk(netdb):
for f in files:
if f.endswith(".dat"):
# TODO check modified time
# may be not older than 10h
dat_files.append(os.path.join(root, f))
if len(dat_files) < 100:
raise PyseederException("Can't get enough netDb entries. Wrong netDb path?")
with ZipFile(zip_file, "w", compression=ZIP_DEFLATED) as zf:
for f in random.sample(dat_files, 75):
zf.write(f, arcname=os.path.split(f)[1])
self.FILE_TYPE = 0x00
self.CONTENT_TYPE = 0x03
self.CONTENT = zip_file.getvalue()
self.CONTENT_LENGTH = len(self.CONTENT)

58
pyseeder/transport.py

@ -0,0 +1,58 @@ @@ -0,0 +1,58 @@
"""Module for managing transport tasks"""
import urllib.request
from urllib.error import URLError
import os, os.path
import importlib
from pyseeder.utils import PyseederException
RESEED_URLS = [
"https://reseed.i2p-projekt.de/",
"https://i2p.mooo.com/netDb/",
"https://netdb.i2p2.no/",
"https://us.reseed.i2p2.no:444/",
"https://uk.reseed.i2p2.no:444/",
"https://i2p.manas.ca:8443/",
"https://i2p-0.manas.ca:8443/",
"https://reseed.i2p.vzaws.com:8443/",
"https://user.mx24.eu/",
"https://download.xxlspeed.com/",
]
def download(url, filename):
"""Download .su3 file, return True on success"""
USER_AGENT = "Wget/1.11.4"
url = "{}i2pseeds.su3".format(url)
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
try:
with urllib.request.urlopen(req) as resp:
with open(filename, 'wb') as f:
f.write(resp.read())
if os.stat(filename).st_size > 0:
return True
else:
return False
except URLError as e:
return False
def upload(filename, config):
"""Upload .su3 file with transports"""
if "transports" in config and "enabled" in config["transports"]:
for t in config["transports"]["enabled"].split():
if t in config:
tconf = config[t]
else:
tconf = None
try:
importlib.import_module("pyseeder.transports.{}".format(t)) \
.run(filename, tconf)
except ImportError:
raise PyseederException(
"{} transport can't be loaded".format(t))

0
pyseeder/transports/__init__.py

3
pyseeder/transports/dropbox.py

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
def run(filename, config):
print("dummy dropbox plugin")

39
pyseeder/transports/git.py

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
"""Git transport plugin"""
import subprocess
import os, os.path
from shutil import copyfile
from pyseeder.utils import TransportException
TRANSPORT_NAME = "git"
# Push to github repo witout prompting password.
# Set up SSH keys or change origin URL like that:
# git remote set-url origin https://$USERNAME:$PASSWORD@github.com/$USERNAME/$REPO.git
def run(filename, config):
if "folder" not in config:
raise TransportException("git: No folder specified in config")
else:
REPO_FOLDER = config["folder"]
REPO_FILE = os.path.split(filename)[1]
if not os.access(REPO_FOLDER, os.W_OK):
raise TransportException("git: {} access forbidden" \
.format(REPO_FOLDER))
if not os.path.isfile(filename):
raise TransportException("git: input file not found")
copyfile(filename, os.path.join(REPO_FOLDER, REPO_FILE))
commands = [
"git add {}".format(REPO_FILE),
"git commit -m 'update'",
"git push origin master"
]
cwd = os.getcwd()
os.chdir(REPO_FOLDER)
for c in commands: subprocess.call(c, shell=True)
os.chdir(cwd)

7
pyseeder/utils.py

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
"""Various code"""
class PyseederException(Exception):
pass
class TransportException(PyseederException):
pass

6
requirements.txt

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
cffi==1.7.0
cryptography==1.4
idna==2.1
pyasn1==0.1.9
pycparser==2.14
six==1.10.0

10
transports.ini.example

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
[transports]
; enabled transports separated by space
enabled=git
[git]
; Folder with git repository to use
folder=/home/user/reseed-data-repo
[dropbox]
; todo
Loading…
Cancel
Save