version 0.3.0, rev187, Trusted authorization sites support, --publish option on signing, cryptSign command line option, OpenSSL enabled on OSX, Crypto verify allows list of valid addresses, Option for version 2 json DB tables, DbCursor SELECT parameters bugfix, Add peer to site on ListModified, Download blind includes when new site added, Publish command better messages, Multi-threaded announce, New http Torrent trackers, Wait for dbschema.json on query, Handle json import errors, More compact writeJson storage command, Testcase for signing and verifying, Workaround to make non target=_top links work, More clean UiWebsocket command route, Send cert_user_id on siteinfo, Notify other local clients on local file modify, Option to wait for file download before sql query, File rules websocket API command, Cert add and select, set websocket API command, Put focus on innerframe, innerloaded wrapper api command to add hashtag, Allow more file error on big sites, Keep worker running after stuked on done task, New more stable openSSL layer that works on OSX, Noparallel parameter bugfix, RateLimit allowed again interval bugfix, Updater skips non-writeable files, Try to close openssl dll before update
This commit is contained in:
parent
c874726aba
commit
7e4f6bd38e
33 changed files with 1716 additions and 595 deletions
|
@ -3,8 +3,8 @@ import ConfigParser
|
||||||
|
|
||||||
class Config(object):
|
class Config(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.version = "0.2.9"
|
self.version = "0.3.0"
|
||||||
self.rev = 134
|
self.rev = 187
|
||||||
self.parser = self.createArguments()
|
self.parser = self.createArguments()
|
||||||
argv = sys.argv[:] # Copy command line arguments
|
argv = sys.argv[:] # Copy command line arguments
|
||||||
argv = self.parseConfig(argv) # Add arguments from config file
|
argv = self.parseConfig(argv) # Add arguments from config file
|
||||||
|
@ -28,10 +28,13 @@ class Config(object):
|
||||||
coffeescript = "type %s | tools\\coffee\\coffee.cmd"
|
coffeescript = "type %s | tools\\coffee\\coffee.cmd"
|
||||||
else:
|
else:
|
||||||
coffeescript = None
|
coffeescript = None
|
||||||
if sys.platform.startswith("Darwin"): # For some reasons openssl doesnt works on mac yet (https://github.com/HelloZeroNet/ZeroNet/issues/94)
|
""" Probably fixed
|
||||||
|
if sys.platform.lower().startswith("darwin"): # For some reasons openssl doesnt works on mac yet (https://github.com/HelloZeroNet/ZeroNet/issues/94)
|
||||||
use_openssl = False
|
use_openssl = False
|
||||||
else:
|
else:
|
||||||
use_openssl = True
|
use_openssl = True
|
||||||
|
"""
|
||||||
|
use_openssl = True
|
||||||
|
|
||||||
# Create parser
|
# Create parser
|
||||||
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
|
@ -49,6 +52,7 @@ class Config(object):
|
||||||
action.add_argument('address', help='Site to sign')
|
action.add_argument('address', help='Site to sign')
|
||||||
action.add_argument('privatekey', help='Private key (default: ask on execute)', nargs='?')
|
action.add_argument('privatekey', help='Private key (default: ask on execute)', nargs='?')
|
||||||
action.add_argument('--inner_path', help='File you want to sign (default: content.json)', default="content.json", metavar="inner_path")
|
action.add_argument('--inner_path', help='File you want to sign (default: content.json)', default="content.json", metavar="inner_path")
|
||||||
|
action.add_argument('--publish', help='Publish site after the signing', action='store_true')
|
||||||
|
|
||||||
# SitePublish
|
# SitePublish
|
||||||
action = subparsers.add_parser("sitePublish", help='Publish site to other peers: address')
|
action = subparsers.add_parser("sitePublish", help='Publish site to other peers: address')
|
||||||
|
@ -89,6 +93,10 @@ class Config(object):
|
||||||
action.add_argument('cmd', help='Command to execute')
|
action.add_argument('cmd', help='Command to execute')
|
||||||
action.add_argument('parameters', help='Parameters to command', nargs='?')
|
action.add_argument('parameters', help='Parameters to command', nargs='?')
|
||||||
|
|
||||||
|
# CryptSign
|
||||||
|
action = subparsers.add_parser("cryptSign", help='Sign message using Bitcoin private key')
|
||||||
|
action.add_argument('message', help='Message to sign')
|
||||||
|
action.add_argument('privatekey', help='Private key')
|
||||||
|
|
||||||
|
|
||||||
# Config parameters
|
# Config parameters
|
||||||
|
|
|
@ -106,7 +106,7 @@ class Connection(object):
|
||||||
self.incomplete_buff_recv = 0
|
self.incomplete_buff_recv = 0
|
||||||
self.handleMessage(message)
|
self.handleMessage(message)
|
||||||
message = None
|
message = None
|
||||||
buf = None
|
buff = None
|
||||||
except Exception, err:
|
except Exception, err:
|
||||||
if not self.closed: self.log("Socket error: %s" % Debug.formatException(err))
|
if not self.closed: self.log("Socket error: %s" % Debug.formatException(err))
|
||||||
self.close() # MessageLoop ended, close connection
|
self.close() # MessageLoop ended, close connection
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import json, time, re, os, gevent
|
import json, time, re, os, gevent, copy
|
||||||
from Debug import Debug
|
from Debug import Debug
|
||||||
from Crypt import CryptHash
|
from Crypt import CryptHash
|
||||||
from Config import config
|
from Config import config
|
||||||
|
@ -18,13 +18,14 @@ class ContentManager:
|
||||||
content_inner_path = content_inner_path.strip("/") # Remove / from begning
|
content_inner_path = content_inner_path.strip("/") # Remove / from begning
|
||||||
old_content = self.contents.get(content_inner_path)
|
old_content = self.contents.get(content_inner_path)
|
||||||
content_path = self.site.storage.getPath(content_inner_path)
|
content_path = self.site.storage.getPath(content_inner_path)
|
||||||
|
content_path_dir = self.toDir(self.site.storage.getPath(content_inner_path))
|
||||||
content_dir = self.toDir(content_inner_path)
|
content_dir = self.toDir(content_inner_path)
|
||||||
|
|
||||||
if os.path.isfile(content_path):
|
if os.path.isfile(content_path):
|
||||||
try:
|
try:
|
||||||
new_content = json.load(open(content_path))
|
new_content = json.load(open(content_path))
|
||||||
except Exception, err:
|
except Exception, err:
|
||||||
self.log.error("Content.json load error: %s" % Debug.formatException(err))
|
self.log.error("%s load error: %s" % (content_path, Debug.formatException(err)))
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
self.log.error("Content.json not exits: %s" % content_path)
|
self.log.error("Content.json not exits: %s" % content_path)
|
||||||
|
@ -58,6 +59,14 @@ class ContentManager:
|
||||||
self.log.debug("Missing include: %s" % include_inner_path)
|
self.log.debug("Missing include: %s" % include_inner_path)
|
||||||
changed += [include_inner_path]
|
changed += [include_inner_path]
|
||||||
|
|
||||||
|
# Load blind user includes (all subdir)
|
||||||
|
if load_includes and "user_contents" in new_content:
|
||||||
|
for relative_dir in os.listdir(content_path_dir):
|
||||||
|
include_inner_path = content_dir+relative_dir+"/content.json"
|
||||||
|
if not self.site.storage.isFile(include_inner_path): continue # Content.json not exits
|
||||||
|
success = self.loadContent(include_inner_path, add_bad_files=add_bad_files, load_includes=False)
|
||||||
|
if success: changed += success # Add changed files
|
||||||
|
|
||||||
# Update the content
|
# Update the content
|
||||||
self.contents[content_inner_path] = new_content
|
self.contents[content_inner_path] = new_content
|
||||||
except Exception, err:
|
except Exception, err:
|
||||||
|
@ -97,19 +106,27 @@ class ContentManager:
|
||||||
content = self.contents.get(content_inner_path.strip("/"))
|
content = self.contents.get(content_inner_path.strip("/"))
|
||||||
if content and "files" in content: # Check if content.json exists
|
if content and "files" in content: # Check if content.json exists
|
||||||
back = content["files"].get("/".join(inner_path_parts))
|
back = content["files"].get("/".join(inner_path_parts))
|
||||||
if not back: return False
|
if back:
|
||||||
back["content_inner_path"] = content_inner_path
|
back["content_inner_path"] = content_inner_path
|
||||||
|
return back
|
||||||
|
|
||||||
|
if content and "user_contents" in content: # User dir
|
||||||
|
back = content["user_contents"]
|
||||||
|
back["content_inner_path"] = re.sub("(.*)/.*?$", "\\1/content.json", inner_path) # Content.json is in the users dir
|
||||||
return back
|
return back
|
||||||
else: # No inner path in this dir, lets try the parent dir
|
|
||||||
if dirs:
|
# No inner path in this dir, lets try the parent dir
|
||||||
inner_path_parts.insert(0, dirs.pop())
|
if dirs:
|
||||||
else: # No more parent dirs
|
inner_path_parts.insert(0, dirs.pop())
|
||||||
break
|
else: # No more parent dirs
|
||||||
|
break
|
||||||
|
|
||||||
return False # Not found
|
return False # Not found
|
||||||
|
|
||||||
|
|
||||||
def getIncludeInfo(self, inner_path):
|
# Get rules for the file
|
||||||
|
# Return: The rules for the file or False if not allowed
|
||||||
|
def getRules(self, inner_path, content=None):
|
||||||
if not inner_path.endswith("content.json"): # Find the files content.json first
|
if not inner_path.endswith("content.json"): # Find the files content.json first
|
||||||
file_info = self.getFileInfo(inner_path)
|
file_info = self.getFileInfo(inner_path)
|
||||||
if not file_info: return False # File not found
|
if not file_info: return False # File not found
|
||||||
|
@ -119,9 +136,11 @@ class ContentManager:
|
||||||
inner_path_parts.insert(0, dirs.pop()) # Dont check in self dir
|
inner_path_parts.insert(0, dirs.pop()) # Dont check in self dir
|
||||||
while True:
|
while True:
|
||||||
content_inner_path = "%s/content.json" % "/".join(dirs)
|
content_inner_path = "%s/content.json" % "/".join(dirs)
|
||||||
content = self.contents.get(content_inner_path.strip("/"))
|
parent_content = self.contents.get(content_inner_path.strip("/"))
|
||||||
if content and "includes" in content:
|
if parent_content and "includes" in parent_content:
|
||||||
return content["includes"].get("/".join(inner_path_parts))
|
return parent_content["includes"].get("/".join(inner_path_parts))
|
||||||
|
elif parent_content and "user_contents" in parent_content:
|
||||||
|
return self.getUserContentRules(parent_content, inner_path, content)
|
||||||
else: # No inner path in this dir, lets try the parent dir
|
else: # No inner path in this dir, lets try the parent dir
|
||||||
if dirs:
|
if dirs:
|
||||||
inner_path_parts.insert(0, dirs.pop())
|
inner_path_parts.insert(0, dirs.pop())
|
||||||
|
@ -131,10 +150,55 @@ class ContentManager:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# Get rules for a user file
|
||||||
|
# Return: The rules of the file or False if not allowed
|
||||||
|
def getUserContentRules(self, parent_content, inner_path, content):
|
||||||
|
user_contents = parent_content["user_contents"]
|
||||||
|
user_address = re.match(".*/([A-Za-z0-9]*?)/.*?$", inner_path).group(1) # Delivered for directory
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not content: content = self.site.storage.loadJson(inner_path) # Read the file if no content specificed
|
||||||
|
except: # Content.json not exits
|
||||||
|
return { "signers": [user_address], "user_address": user_address } # Return information that we know for sure
|
||||||
|
|
||||||
|
"""if not "cert_user_name" in content: # New file, unknown user
|
||||||
|
content["cert_auth_type"] = "unknown"
|
||||||
|
content["cert_user_name"] = "unknown@unknown"
|
||||||
|
"""
|
||||||
|
user_urn = "%s/%s" % (content["cert_auth_type"], content["cert_user_id"]) # web/nofish@zeroid.bit
|
||||||
|
|
||||||
|
rules = copy.copy(user_contents["permissions"].get(content["cert_user_id"], {})) # Default rules by username
|
||||||
|
if rules == False: return False # User banned
|
||||||
|
if "signers" in rules: rules["signers"] = rules["signers"][:] # Make copy of the signers
|
||||||
|
for permission_pattern, permission_rules in user_contents["permission_rules"].items(): # Regexp rules
|
||||||
|
if not re.match(permission_pattern, user_urn): continue # Rule is not valid for user
|
||||||
|
# Update rules if its better than current recorded ones
|
||||||
|
for key, val in permission_rules.iteritems():
|
||||||
|
if key not in rules:
|
||||||
|
if type(val) is list:
|
||||||
|
rules[key] = val[:] # Make copy
|
||||||
|
else:
|
||||||
|
rules[key] = val
|
||||||
|
elif type(val) is int: # Int, update if larger
|
||||||
|
if val > rules[key]: rules[key] = val
|
||||||
|
elif hasattr(val, "startswith"): # String, update if longer
|
||||||
|
if len(val) > len(rules[key]): rules[key] = val
|
||||||
|
elif type(val) is list: # List, append
|
||||||
|
rules[key] += val
|
||||||
|
|
||||||
|
rules["cert_signers"] = user_contents["cert_signers"] # Add valid cert signers
|
||||||
|
if "signers" not in rules: rules["signers"] = []
|
||||||
|
rules["signers"].append(user_address) # Add user as valid signer
|
||||||
|
rules["user_address"] = user_address
|
||||||
|
|
||||||
|
|
||||||
|
return rules
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Create and sign a content.json
|
# Create and sign a content.json
|
||||||
# Return: The new content if filewrite = False
|
# Return: The new content if filewrite = False
|
||||||
def sign(self, inner_path = "content.json", privatekey=None, filewrite=True, update_changed_files=False):
|
def sign(self, inner_path = "content.json", privatekey=None, filewrite=True, update_changed_files=False, extend=None):
|
||||||
content = self.contents.get(inner_path)
|
content = self.contents.get(inner_path)
|
||||||
if not content: # Content not exits yet, load default one
|
if not content: # Content not exits yet, load default one
|
||||||
self.log.info("File %s not exits yet, loading default values..." % inner_path)
|
self.log.info("File %s not exits yet, loading default values..." % inner_path)
|
||||||
|
@ -144,6 +208,7 @@ class ContentManager:
|
||||||
content["description"] = ""
|
content["description"] = ""
|
||||||
content["signs_required"] = 1
|
content["signs_required"] = 1
|
||||||
content["ignore"] = ""
|
content["ignore"] = ""
|
||||||
|
if extend: content.update(extend) # Add custom fields
|
||||||
|
|
||||||
directory = self.toDir(self.site.storage.getPath(inner_path))
|
directory = self.toDir(self.site.storage.getPath(inner_path))
|
||||||
self.log.info("Opening site data directory: %s..." % directory)
|
self.log.info("Opening site data directory: %s..." % directory)
|
||||||
|
@ -154,8 +219,13 @@ class ContentManager:
|
||||||
for file_name in files:
|
for file_name in files:
|
||||||
file_path = self.site.storage.getPath("%s/%s" % (root.strip("/"), file_name))
|
file_path = self.site.storage.getPath("%s/%s" % (root.strip("/"), file_name))
|
||||||
file_inner_path = re.sub(re.escape(directory), "", file_path)
|
file_inner_path = re.sub(re.escape(directory), "", file_path)
|
||||||
|
|
||||||
if file_name == "content.json" or (content.get("ignore") and re.match(content["ignore"], file_inner_path)) or file_name.startswith("."): # Ignore content.json, definied regexp and files starting with .
|
if file_name == "content.json": ignored = True
|
||||||
|
elif content.get("ignore") and re.match(content["ignore"], file_inner_path): ignored = True
|
||||||
|
elif file_name.startswith("."): ignored = True
|
||||||
|
else: ignored = False
|
||||||
|
|
||||||
|
if ignored: # Ignore content.json, definied regexp and files starting with .
|
||||||
self.log.info("- [SKIPPED] %s" % file_inner_path)
|
self.log.info("- [SKIPPED] %s" % file_inner_path)
|
||||||
else:
|
else:
|
||||||
sha512sum = CryptHash.sha512sum(file_path) # Calculate sha512 sum of file
|
sha512sum = CryptHash.sha512sum(file_path) # Calculate sha512 sum of file
|
||||||
|
@ -184,7 +254,7 @@ class ContentManager:
|
||||||
from Crypt import CryptBitcoin
|
from Crypt import CryptBitcoin
|
||||||
self.log.info("Verifying private key...")
|
self.log.info("Verifying private key...")
|
||||||
privatekey_address = CryptBitcoin.privatekeyToAddress(privatekey)
|
privatekey_address = CryptBitcoin.privatekeyToAddress(privatekey)
|
||||||
valid_signers = self.getValidSigners(inner_path)
|
valid_signers = self.getValidSigners(inner_path, new_content)
|
||||||
if privatekey_address not in valid_signers:
|
if privatekey_address not in valid_signers:
|
||||||
return self.log.error("Private key invalid! Valid signers: %s, Private key address: %s" % (valid_signers, privatekey_address))
|
return self.log.error("Private key invalid! Valid signers: %s, Private key address: %s" % (valid_signers, privatekey_address))
|
||||||
self.log.info("Correct %s in valid signers: %s" % (privatekey_address, valid_signers))
|
self.log.info("Correct %s in valid signers: %s" % (privatekey_address, valid_signers))
|
||||||
|
@ -215,7 +285,7 @@ class ContentManager:
|
||||||
|
|
||||||
if filewrite:
|
if filewrite:
|
||||||
self.log.info("Saving to %s..." % inner_path)
|
self.log.info("Saving to %s..." % inner_path)
|
||||||
json.dump(new_content, open(self.site.storage.getPath(inner_path), "w"), indent=2, sort_keys=True)
|
self.site.storage.writeJson(inner_path, new_content)
|
||||||
|
|
||||||
self.log.info("File %s signed!" % inner_path)
|
self.log.info("File %s signed!" % inner_path)
|
||||||
|
|
||||||
|
@ -227,25 +297,39 @@ class ContentManager:
|
||||||
|
|
||||||
# The valid signers of content.json file
|
# The valid signers of content.json file
|
||||||
# Return: ["1KRxE1s3oDyNDawuYWpzbLUwNm8oDbeEp6", "13ReyhCsjhpuCVahn1DHdf6eMqqEVev162"]
|
# Return: ["1KRxE1s3oDyNDawuYWpzbLUwNm8oDbeEp6", "13ReyhCsjhpuCVahn1DHdf6eMqqEVev162"]
|
||||||
def getValidSigners(self, inner_path):
|
def getValidSigners(self, inner_path, content=None):
|
||||||
valid_signers = []
|
valid_signers = []
|
||||||
if inner_path == "content.json": # Root content.json
|
if inner_path == "content.json": # Root content.json
|
||||||
if "content.json" in self.contents and "signers" in self.contents["content.json"]:
|
if "content.json" in self.contents and "signers" in self.contents["content.json"]:
|
||||||
valid_signers += self.contents["content.json"]["signers"].keys()
|
valid_signers += self.contents["content.json"]["signers"].keys()
|
||||||
else:
|
else:
|
||||||
include_info = self.getIncludeInfo(inner_path)
|
rules = self.getRules(inner_path, content)
|
||||||
if include_info and "signers" in include_info:
|
if rules and "signers" in rules:
|
||||||
valid_signers += include_info["signers"]
|
valid_signers += rules["signers"]
|
||||||
|
|
||||||
if self.site.address not in valid_signers: valid_signers.append(self.site.address) # Site address always valid
|
if self.site.address not in valid_signers: valid_signers.append(self.site.address) # Site address always valid
|
||||||
return valid_signers
|
return valid_signers
|
||||||
|
|
||||||
|
|
||||||
# Return: The required number of valid signs for the content.json
|
# Return: The required number of valid signs for the content.json
|
||||||
def getSignsRequired(self, inner_path):
|
def getSignsRequired(self, inner_path, content=None):
|
||||||
return 1 # Todo: Multisig
|
return 1 # Todo: Multisig
|
||||||
|
|
||||||
|
|
||||||
|
def verifyCert(self, inner_path, content):
|
||||||
|
from Crypt import CryptBitcoin
|
||||||
|
|
||||||
|
rules = self.getRules(inner_path, content)
|
||||||
|
if not rules.get("cert_signers"): return True # Does not need cert
|
||||||
|
|
||||||
|
name, domain = content["cert_user_id"].split("@")
|
||||||
|
cert_address = rules["cert_signers"].get(domain)
|
||||||
|
if not cert_address: # Cert signer not allowed
|
||||||
|
self.log.error("Invalid cert signer: %s" % domain)
|
||||||
|
return False
|
||||||
|
return CryptBitcoin.verify("%s#%s/%s" % (rules["user_address"], content["cert_auth_type"], name), cert_address, content["cert_sign"])
|
||||||
|
|
||||||
|
|
||||||
# Checks if the content.json content is valid
|
# Checks if the content.json content is valid
|
||||||
# Return: True or False
|
# Return: True or False
|
||||||
def validContent(self, inner_path, content):
|
def validContent(self, inner_path, content):
|
||||||
|
@ -266,26 +350,26 @@ class ContentManager:
|
||||||
if inner_path == "content.json": return True # Root content.json is passed
|
if inner_path == "content.json": return True # Root content.json is passed
|
||||||
|
|
||||||
# Load include details
|
# Load include details
|
||||||
include_info = self.getIncludeInfo(inner_path)
|
rules = self.getRules(inner_path, content)
|
||||||
if not include_info:
|
if not rules:
|
||||||
self.log.error("%s: No include info" % inner_path)
|
self.log.error("%s: No rules" % inner_path)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check include size limit
|
# Check include size limit
|
||||||
if include_info.get("max_size"): # Include size limit
|
if rules.get("max_size"): # Include size limit
|
||||||
if content_size > include_info["max_size"]:
|
if content_size > rules["max_size"]:
|
||||||
self.log.error("%s: Include too large %s > %s" % (inner_path, content_size, include_info["max_size"]))
|
self.log.error("%s: Include too large %s > %s" % (inner_path, content_size, rules["max_size"]))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check if content includes allowed
|
# Check if content includes allowed
|
||||||
if include_info.get("includes_allowed") == False and content.get("includes"):
|
if rules.get("includes_allowed") == False and content.get("includes"):
|
||||||
self.log.error("%s: Includes not allowed" % inner_path)
|
self.log.error("%s: Includes not allowed" % inner_path)
|
||||||
return False # Includes not allowed
|
return False # Includes not allowed
|
||||||
|
|
||||||
# Filename limit
|
# Filename limit
|
||||||
if include_info.get("files_allowed"):
|
if rules.get("files_allowed"):
|
||||||
for file_inner_path in content["files"].keys():
|
for file_inner_path in content["files"].keys():
|
||||||
if not re.match("^%s$" % include_info["files_allowed"], file_inner_path):
|
if not re.match("^%s$" % rules["files_allowed"], file_inner_path):
|
||||||
self.log.error("%s: File not allowed" % file_inner_path)
|
self.log.error("%s: File not allowed" % file_inner_path)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -322,19 +406,25 @@ class ContentManager:
|
||||||
if not self.validContent(inner_path, new_content): return False # Content not valid (files too large, invalid files)
|
if not self.validContent(inner_path, new_content): return False # Content not valid (files too large, invalid files)
|
||||||
|
|
||||||
if signs: # New style signing
|
if signs: # New style signing
|
||||||
valid_signers = self.getValidSigners(inner_path)
|
valid_signers = self.getValidSigners(inner_path, new_content)
|
||||||
signs_required = self.getSignsRequired(inner_path)
|
signs_required = self.getSignsRequired(inner_path, new_content)
|
||||||
|
|
||||||
if inner_path == "content.json" and len(valid_signers) > 1: # Check signers_sign on root content.json
|
if inner_path == "content.json" and len(valid_signers) > 1: # Check signers_sign on root content.json
|
||||||
if not CryptBitcoin.verify("%s:%s" % (signs_required, ",".join(valid_signers)), self.site.address, new_content["signers_sign"]):
|
if not CryptBitcoin.verify("%s:%s" % (signs_required, ",".join(valid_signers)), self.site.address, new_content["signers_sign"]):
|
||||||
self.log.error("%s invalid signers_sign!" % inner_path)
|
self.log.error("%s invalid signers_sign!" % inner_path)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if inner_path != "content.json" and not self.verifyCert(inner_path, new_content): # Check if cert valid
|
||||||
|
self.log.error("%s invalid cert!" % inner_path)
|
||||||
|
return False
|
||||||
|
|
||||||
valid_signs = 0
|
valid_signs = 0
|
||||||
for address in valid_signers:
|
for address in valid_signers:
|
||||||
if address in signs: valid_signs += CryptBitcoin.verify(sign_content, address, signs[address])
|
if address in signs: valid_signs += CryptBitcoin.verify(sign_content, address, signs[address])
|
||||||
if valid_signs >= signs_required: break # Break if we has enough signs
|
if valid_signs >= signs_required: break # Break if we has enough signs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return valid_signs >= signs_required
|
return valid_signs >= signs_required
|
||||||
else: # Old style signing
|
else: # Old style signing
|
||||||
return CryptBitcoin.verify(sign_content, self.site.address, sign)
|
return CryptBitcoin.verify(sign_content, self.site.address, sign)
|
||||||
|
@ -348,8 +438,10 @@ class ContentManager:
|
||||||
if file_info:
|
if file_info:
|
||||||
if "sha512" in file_info:
|
if "sha512" in file_info:
|
||||||
hash_valid = CryptHash.sha512sum(file) == file_info["sha512"]
|
hash_valid = CryptHash.sha512sum(file) == file_info["sha512"]
|
||||||
else: # Backward compatibility
|
elif "sha1" in file_info: # Backward compatibility
|
||||||
hash_valid = CryptHash.sha1sum(file) == file_info["sha1"]
|
hash_valid = CryptHash.sha1sum(file) == file_info["sha1"]
|
||||||
|
else:
|
||||||
|
hash_valid = False
|
||||||
if file_info["size"] != file.tell():
|
if file_info["size"] != file.tell():
|
||||||
self.log.error("%s file size does not match %s <> %s, Hash: %s" % (inner_path, file.tell(), file_info["size"], hash_valid))
|
self.log.error("%s file size does not match %s <> %s, Hash: %s" % (inner_path, file.tell(), file_info["size"], hash_valid))
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -62,7 +62,11 @@ def verify(data, address, sign): # Verify data using address and sign
|
||||||
else: # Use pure-python
|
else: # Use pure-python
|
||||||
pub = btctools.ecdsa_recover(data, sign)
|
pub = btctools.ecdsa_recover(data, sign)
|
||||||
sign_address = btctools.pubtoaddr(pub)
|
sign_address = btctools.pubtoaddr(pub)
|
||||||
return sign_address == address
|
|
||||||
|
if type(address) is list: # Any address in the list
|
||||||
|
return sign_address in address
|
||||||
|
else: # One possible address
|
||||||
|
return sign_address == address
|
||||||
else: # Backward compatible old style
|
else: # Backward compatible old style
|
||||||
bitcoin = BitcoinECC.Bitcoin()
|
bitcoin = BitcoinECC.Bitcoin()
|
||||||
return bitcoin.VerifyMessageFromBitcoinAddress(address, data, sign)
|
return bitcoin.VerifyMessageFromBitcoinAddress(address, data, sign)
|
||||||
|
|
26
src/Db/Db.py
26
src/Db/Db.py
|
@ -6,6 +6,7 @@ class Db:
|
||||||
self.db_path = db_path
|
self.db_path = db_path
|
||||||
self.db_dir = os.path.dirname(db_path)+"/"
|
self.db_dir = os.path.dirname(db_path)+"/"
|
||||||
self.schema = schema
|
self.schema = schema
|
||||||
|
self.schema["version"] = self.schema.get("version", 1)
|
||||||
self.conn = None
|
self.conn = None
|
||||||
self.cur = None
|
self.cur = None
|
||||||
self.log = logging.getLogger("Db:%s" % schema["db_name"])
|
self.log = logging.getLogger("Db:%s" % schema["db_name"])
|
||||||
|
@ -85,6 +86,7 @@ class Db:
|
||||||
cur.execute("BEGIN")
|
cur.execute("BEGIN")
|
||||||
|
|
||||||
# Check internal tables
|
# Check internal tables
|
||||||
|
# Check keyvalue table
|
||||||
changed = cur.needTable("keyvalue", [
|
changed = cur.needTable("keyvalue", [
|
||||||
["keyvalue_id", "INTEGER PRIMARY KEY AUTOINCREMENT"],
|
["keyvalue_id", "INTEGER PRIMARY KEY AUTOINCREMENT"],
|
||||||
["key", "TEXT"],
|
["key", "TEXT"],
|
||||||
|
@ -92,15 +94,25 @@ class Db:
|
||||||
["json_id", "INTEGER REFERENCES json (json_id)"],
|
["json_id", "INTEGER REFERENCES json (json_id)"],
|
||||||
],[
|
],[
|
||||||
"CREATE UNIQUE INDEX key_id ON keyvalue(json_id, key)"
|
"CREATE UNIQUE INDEX key_id ON keyvalue(json_id, key)"
|
||||||
], version=1)
|
], version=self.schema["version"])
|
||||||
if changed: changed_tables.append("keyvalue")
|
if changed: changed_tables.append("keyvalue")
|
||||||
|
|
||||||
changed = cur.needTable("json", [
|
# Check json table
|
||||||
["json_id", "INTEGER PRIMARY KEY AUTOINCREMENT"],
|
if self.schema["version"] == 1:
|
||||||
["path", "VARCHAR(255)"]
|
changed = cur.needTable("json", [
|
||||||
], [
|
["json_id", "INTEGER PRIMARY KEY AUTOINCREMENT"],
|
||||||
"CREATE UNIQUE INDEX path ON json(path)"
|
["path", "VARCHAR(255)"]
|
||||||
], version=1)
|
], [
|
||||||
|
"CREATE UNIQUE INDEX path ON json(path)"
|
||||||
|
], version=self.schema["version"])
|
||||||
|
else:
|
||||||
|
changed = cur.needTable("json", [
|
||||||
|
["json_id", "INTEGER PRIMARY KEY AUTOINCREMENT"],
|
||||||
|
["directory", "VARCHAR(255)"],
|
||||||
|
["file_name", "VARCHAR(255)"]
|
||||||
|
], [
|
||||||
|
"CREATE UNIQUE INDEX path ON json(directory, file_name)"
|
||||||
|
], version=self.schema["version"])
|
||||||
if changed: changed_tables.append("json")
|
if changed: changed_tables.append("json")
|
||||||
|
|
||||||
# Check schema tables
|
# Check schema tables
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import time
|
import time, re
|
||||||
|
|
||||||
# Special sqlite cursor
|
# Special sqlite cursor
|
||||||
class DbCursor:
|
class DbCursor:
|
||||||
|
@ -12,7 +12,7 @@ class DbCursor:
|
||||||
def execute(self, query, params=None):
|
def execute(self, query, params=None):
|
||||||
if isinstance(params, dict): # Make easier select and insert by allowing dict params
|
if isinstance(params, dict): # Make easier select and insert by allowing dict params
|
||||||
if query.startswith("SELECT") or query.startswith("DELETE"): # Convert param dict to SELECT * FROM table WHERE key = ?, key2 = ? format
|
if query.startswith("SELECT") or query.startswith("DELETE"): # Convert param dict to SELECT * FROM table WHERE key = ?, key2 = ? format
|
||||||
wheres = ", ".join([key+" = ?" for key in params])
|
wheres = "AND ".join([key+" = ?" for key in params])
|
||||||
query = query.replace("?", wheres)
|
query = query.replace("?", wheres)
|
||||||
params = params.values()
|
params = params.values()
|
||||||
else: # Convert param dict to INSERT INTO table (key, key2) VALUES (?, ?) format
|
else: # Convert param dict to INSERT INTO table (key, key2) VALUES (?, ?) format
|
||||||
|
@ -94,12 +94,21 @@ class DbCursor:
|
||||||
# Get or create a row for json file
|
# Get or create a row for json file
|
||||||
# Return: The database row
|
# Return: The database row
|
||||||
def getJsonRow(self, file_path):
|
def getJsonRow(self, file_path):
|
||||||
res = self.execute("SELECT * FROM json WHERE ? LIMIT 1", {"path": file_path})
|
directory, file_name = re.match("^(.*?)/*([^/]*)$", file_path).groups()
|
||||||
row = res.fetchone()
|
if self.db.schema["version"] == 1:
|
||||||
if not row: # No row yet, create it
|
|
||||||
self.execute("INSERT INTO json ?", {"path": file_path})
|
|
||||||
res = self.execute("SELECT * FROM json WHERE ? LIMIT 1", {"path": file_path})
|
res = self.execute("SELECT * FROM json WHERE ? LIMIT 1", {"path": file_path})
|
||||||
row = res.fetchone()
|
row = res.fetchone()
|
||||||
|
if not row: # No row yet, create it
|
||||||
|
self.execute("INSERT INTO json ?", {"path": file_path})
|
||||||
|
res = self.execute("SELECT * FROM json WHERE ? LIMIT 1", {"path": file_path})
|
||||||
|
row = res.fetchone()
|
||||||
|
else:
|
||||||
|
res = self.execute("SELECT * FROM json WHERE ? LIMIT 1", {"directory": directory, "file_name": file_name})
|
||||||
|
row = res.fetchone()
|
||||||
|
if not row: # No row yet, create it
|
||||||
|
self.execute("INSERT INTO json ?", {"directory": directory, "file_name": file_name})
|
||||||
|
res = self.execute("SELECT * FROM json WHERE ? LIMIT 1", {"directory": directory, "file_name": file_name})
|
||||||
|
row = res.fetchone()
|
||||||
return row
|
return row
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
|
|
@ -170,6 +170,12 @@ class FileRequest(object):
|
||||||
self.response({"error": "Unknown site"})
|
self.response({"error": "Unknown site"})
|
||||||
return False
|
return False
|
||||||
modified_files = {inner_path: content["modified"] for inner_path, content in site.content_manager.contents.iteritems() if content["modified"] > params["since"]}
|
modified_files = {inner_path: content["modified"] for inner_path, content in site.content_manager.contents.iteritems() if content["modified"] > params["since"]}
|
||||||
|
|
||||||
|
# Add peer to site if not added before
|
||||||
|
connected_peer = site.addPeer(self.connection.ip, self.connection.port)
|
||||||
|
if connected_peer: # Just added
|
||||||
|
connected_peer.connect(self.connection) # Assign current connection to peer
|
||||||
|
|
||||||
self.response({"modified_files": modified_files})
|
self.response({"modified_files": modified_files})
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -131,6 +131,7 @@ class Peer(object):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
buff.write(back["body"])
|
buff.write(back["body"])
|
||||||
|
back["body"] = None # Save memory
|
||||||
if back["location"] == back["size"]: # End of file
|
if back["location"] == back["size"]: # End of file
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
|
188
src/Site/Site.py
188
src/Site/Site.py
|
@ -151,17 +151,18 @@ class Site:
|
||||||
self.log.debug("Start downloading...%s" % self.bad_files)
|
self.log.debug("Start downloading...%s" % self.bad_files)
|
||||||
gevent.spawn(self.announce)
|
gevent.spawn(self.announce)
|
||||||
if check_size: # Check the size first
|
if check_size: # Check the size first
|
||||||
valid = downloadContent(download_files=False)
|
valid = downloadContent(download_files=False) # Just download content.json files
|
||||||
if not valid: return False # Cant download content.jsons or size is not fits
|
if not valid: return False # Cant download content.jsons or size is not fits
|
||||||
|
|
||||||
|
# Download everything
|
||||||
found = self.downloadContent("content.json")
|
found = self.downloadContent("content.json")
|
||||||
|
self.checkModifications(0) # Download multiuser blind includes
|
||||||
|
|
||||||
return found
|
return found
|
||||||
|
|
||||||
|
|
||||||
# Update worker, try to find client that supports listModifications command
|
# Update worker, try to find client that supports listModifications command
|
||||||
def updater(self, peers_try, queried):
|
def updater(self, peers_try, queried, since):
|
||||||
since = self.settings.get("modified", 60*60*24)-60*60*24 # Get modified since last update - 1day
|
|
||||||
while 1:
|
while 1:
|
||||||
if not peers_try or len(queried) >= 3: # Stop after 3 successful query
|
if not peers_try or len(queried) >= 3: # Stop after 3 successful query
|
||||||
break
|
break
|
||||||
|
@ -179,16 +180,9 @@ class Site:
|
||||||
gevent.spawn(self.downloadContent, inner_path) # Download the content.json + the changed files
|
gevent.spawn(self.downloadContent, inner_path) # Download the content.json + the changed files
|
||||||
|
|
||||||
|
|
||||||
|
# Check modified content.json files from peers and add modified files to bad_files
|
||||||
# Update content.json from peers and download changed files
|
# Return: Successfully queried peers [Peer, Peer...]
|
||||||
# Return: None
|
def checkModifications(self, since=None):
|
||||||
@util.Noparallel()
|
|
||||||
def update(self, announce=False):
|
|
||||||
self.content_manager.loadContent("content.json") # Reload content.json
|
|
||||||
self.content_updated = None # Reset content updated time
|
|
||||||
self.updateWebsocket(updating=True)
|
|
||||||
if announce: self.announce()
|
|
||||||
|
|
||||||
peers_try = [] # Try these peers
|
peers_try = [] # Try these peers
|
||||||
queried = [] # Successfully queried from these peers
|
queried = [] # Successfully queried from these peers
|
||||||
|
|
||||||
|
@ -200,15 +194,30 @@ class Site:
|
||||||
elif len(peers_try) < 5: # Backup peers, add to end of the try list
|
elif len(peers_try) < 5: # Backup peers, add to end of the try list
|
||||||
peers_try.append(peer)
|
peers_try.append(peer)
|
||||||
|
|
||||||
self.log.debug("Try to get listModifications from peers: %s" % peers_try)
|
if since == None: # No since definied, download from last modification time-1day
|
||||||
|
since = self.settings.get("modified", 60*60*24)-60*60*24
|
||||||
|
self.log.debug("Try to get listModifications from peers: %s since: %s" % (peers_try, since))
|
||||||
|
|
||||||
updaters = []
|
updaters = []
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
updaters.append(gevent.spawn(self.updater, peers_try, queried))
|
updaters.append(gevent.spawn(self.updater, peers_try, queried, since))
|
||||||
|
|
||||||
gevent.joinall(updaters, timeout=5) # Wait 5 sec to workers
|
gevent.joinall(updaters, timeout=5) # Wait 5 sec to workers
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
self.log.debug("Queried listModifications from: %s" % queried)
|
self.log.debug("Queried listModifications from: %s" % queried)
|
||||||
|
return queried
|
||||||
|
|
||||||
|
|
||||||
|
# Update content.json from peers and download changed files
|
||||||
|
# Return: None
|
||||||
|
@util.Noparallel()
|
||||||
|
def update(self, announce=False):
|
||||||
|
self.content_manager.loadContent("content.json") # Reload content.json
|
||||||
|
self.content_updated = None # Reset content updated time
|
||||||
|
self.updateWebsocket(updating=True)
|
||||||
|
if announce: self.announce()
|
||||||
|
|
||||||
|
queried = self.checkModifications()
|
||||||
|
|
||||||
if not queried: # Not found any client that supports listModifications
|
if not queried: # Not found any client that supports listModifications
|
||||||
self.log.debug("Fallback to old-style update")
|
self.log.debug("Fallback to old-style update")
|
||||||
|
@ -279,10 +288,11 @@ class Site:
|
||||||
# Update content.json on peers
|
# Update content.json on peers
|
||||||
@util.Noparallel()
|
@util.Noparallel()
|
||||||
def publish(self, limit=5, inner_path="content.json"):
|
def publish(self, limit=5, inner_path="content.json"):
|
||||||
self.log.info( "Publishing to %s/%s peers..." % (limit, len(self.peers)) )
|
self.log.info( "Publishing to %s/%s peers..." % (min(len(self.peers), limit), len(self.peers)) )
|
||||||
published = [] # Successfully published (Peer)
|
published = [] # Successfully published (Peer)
|
||||||
publishers = [] # Publisher threads
|
publishers = [] # Publisher threads
|
||||||
peers = self.peers.values()
|
peers = self.peers.values()
|
||||||
|
if not peers: return 0 # No peers found
|
||||||
|
|
||||||
random.shuffle(peers)
|
random.shuffle(peers)
|
||||||
event_done = gevent.event.AsyncResult()
|
event_done = gevent.event.AsyncResult()
|
||||||
|
@ -381,12 +391,79 @@ class Site:
|
||||||
self.log.debug("Queried pex from %s peers got %s new peers." % (done, added))
|
self.log.debug("Queried pex from %s peers got %s new peers." % (done, added))
|
||||||
|
|
||||||
|
|
||||||
|
# Gather peers from tracker
|
||||||
|
# Return: Complete time or False on error
|
||||||
|
def announceTracker(self, protocol, ip, port, fileserver_port, address_hash, my_peer_id):
|
||||||
|
s = time.time()
|
||||||
|
if protocol == "udp": # Udp tracker
|
||||||
|
if config.disable_udp: return False # No udp supported
|
||||||
|
tracker = UdpTrackerClient(ip, port)
|
||||||
|
tracker.peer_port = fileserver_port
|
||||||
|
try:
|
||||||
|
tracker.connect()
|
||||||
|
tracker.poll_once()
|
||||||
|
tracker.announce(info_hash=address_hash, num_want=50)
|
||||||
|
back = tracker.poll_once()
|
||||||
|
peers = back["response"]["peers"]
|
||||||
|
except Exception, err:
|
||||||
|
return False
|
||||||
|
|
||||||
|
else: # Http tracker
|
||||||
|
params = {
|
||||||
|
'info_hash': binascii.a2b_hex(address_hash),
|
||||||
|
'peer_id': my_peer_id, 'port': fileserver_port,
|
||||||
|
'uploaded': 0, 'downloaded': 0, 'left': 0, 'compact': 1, 'numwant': 30,
|
||||||
|
'event': 'started'
|
||||||
|
}
|
||||||
|
req = None
|
||||||
|
try:
|
||||||
|
url = "http://"+ip+"?"+urllib.urlencode(params)
|
||||||
|
# Load url
|
||||||
|
with gevent.Timeout(10, False): # Make sure of timeout
|
||||||
|
req = urllib2.urlopen(url, timeout=8)
|
||||||
|
response = req.read()
|
||||||
|
req.fp._sock.recv=None # Hacky avoidance of memory leak for older python versions
|
||||||
|
req.close()
|
||||||
|
req = None
|
||||||
|
if not response:
|
||||||
|
self.log.debug("Http tracker %s response error" % url)
|
||||||
|
return False
|
||||||
|
# Decode peers
|
||||||
|
peer_data = bencode.decode(response)["peers"]
|
||||||
|
response = None
|
||||||
|
peer_count = len(peer_data) / 6
|
||||||
|
peers = []
|
||||||
|
for peer_offset in xrange(peer_count):
|
||||||
|
off = 6 * peer_offset
|
||||||
|
peer = peer_data[off:off + 6]
|
||||||
|
addr, port = struct.unpack('!LH', peer)
|
||||||
|
peers.append({"addr": socket.inet_ntoa(struct.pack('!L', addr)), "port": port})
|
||||||
|
except Exception, err:
|
||||||
|
self.log.debug("Http tracker %s error: %s" % (url, err))
|
||||||
|
if req:
|
||||||
|
req.close()
|
||||||
|
req = None
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Adding peers
|
||||||
|
added = 0
|
||||||
|
for peer in peers:
|
||||||
|
if not peer["port"]: continue # Dont add peers with port 0
|
||||||
|
if self.addPeer(peer["addr"], peer["port"]): added += 1
|
||||||
|
if added:
|
||||||
|
self.worker_manager.onPeers()
|
||||||
|
self.updateWebsocket(peers_added=added)
|
||||||
|
self.log.debug("Found %s peers, new: %s" % (len(peers), added))
|
||||||
|
return time.time()-s
|
||||||
|
|
||||||
|
|
||||||
# Add myself and get other peers from tracker
|
# Add myself and get other peers from tracker
|
||||||
def announce(self, force=False):
|
def announce(self, force=False):
|
||||||
if time.time() < self.last_announce+30 and not force: return # No reannouncing within 30 secs
|
if time.time() < self.last_announce+30 and not force: return # No reannouncing within 30 secs
|
||||||
self.last_announce = time.time()
|
self.last_announce = time.time()
|
||||||
errors = []
|
errors = []
|
||||||
address_hash = hashlib.sha1(self.address).hexdigest()
|
slow = []
|
||||||
|
address_hash = hashlib.sha1(self.address).hexdigest() # Site address hash
|
||||||
my_peer_id = sys.modules["main"].file_server.peer_id
|
my_peer_id = sys.modules["main"].file_server.peer_id
|
||||||
|
|
||||||
if sys.modules["main"].file_server.port_opened:
|
if sys.modules["main"].file_server.port_opened:
|
||||||
|
@ -396,73 +473,30 @@ class Site:
|
||||||
|
|
||||||
s = time.time()
|
s = time.time()
|
||||||
announced = 0
|
announced = 0
|
||||||
|
threads = []
|
||||||
|
|
||||||
for protocol, ip, port in SiteManager.TRACKERS:
|
for protocol, ip, port in SiteManager.TRACKERS: # Start announce threads
|
||||||
if protocol == "udp": # Udp tracker
|
thread = gevent.spawn(self.announceTracker, protocol, ip, port, fileserver_port, address_hash, my_peer_id)
|
||||||
if config.disable_udp: continue # No udp supported
|
threads.append(thread)
|
||||||
tracker = UdpTrackerClient(ip, port)
|
thread.ip = ip
|
||||||
tracker.peer_port = fileserver_port
|
thread.protocol = protocol
|
||||||
try:
|
|
||||||
tracker.connect()
|
|
||||||
tracker.poll_once()
|
|
||||||
tracker.announce(info_hash=address_hash, num_want=50)
|
|
||||||
back = tracker.poll_once()
|
|
||||||
peers = back["response"]["peers"]
|
|
||||||
except Exception, err:
|
|
||||||
errors.append("%s://%s:%s" % (protocol, ip, port))
|
|
||||||
continue
|
|
||||||
|
|
||||||
else: # Http tracker
|
|
||||||
params = {
|
|
||||||
'info_hash': binascii.a2b_hex(address_hash),
|
|
||||||
'peer_id': my_peer_id, 'port': fileserver_port,
|
|
||||||
'uploaded': 0, 'downloaded': 0, 'left': 0, 'compact': 1, 'numwant': 30,
|
|
||||||
'event': 'started'
|
|
||||||
}
|
|
||||||
req = None
|
|
||||||
try:
|
|
||||||
url = "http://"+ip+"?"+urllib.urlencode(params)
|
|
||||||
# Load url
|
|
||||||
req = urllib2.urlopen(url, timeout=10)
|
|
||||||
response = req.read()
|
|
||||||
req.fp._sock.recv=None # Hacky avoidance of memory leak for older python versions
|
|
||||||
req.close()
|
|
||||||
req = None
|
|
||||||
# Decode peers
|
|
||||||
peer_data = bencode.decode(response)["peers"]
|
|
||||||
response = None
|
|
||||||
peer_count = len(peer_data) / 6
|
|
||||||
peers = []
|
|
||||||
for peer_offset in xrange(peer_count):
|
|
||||||
off = 6 * peer_offset
|
|
||||||
peer = peer_data[off:off + 6]
|
|
||||||
addr, port = struct.unpack('!LH', peer)
|
|
||||||
peers.append({"addr": socket.inet_ntoa(struct.pack('!L', addr)), "port": port})
|
|
||||||
except Exception, err:
|
|
||||||
self.log.debug("Http tracker %s error: %s" % (url, err))
|
|
||||||
errors.append("%s://%s" % (protocol, ip))
|
|
||||||
if req:
|
|
||||||
req.close()
|
|
||||||
req = None
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Adding peers
|
|
||||||
added = 0
|
|
||||||
for peer in peers:
|
|
||||||
if not peer["port"]: continue # Dont add peers with port 0
|
|
||||||
if self.addPeer(peer["addr"], peer["port"]): added += 1
|
|
||||||
if added:
|
|
||||||
self.worker_manager.onPeers()
|
|
||||||
self.updateWebsocket(peers_added=added)
|
|
||||||
self.log.debug("Found %s peers, new: %s" % (len(peers), added))
|
|
||||||
announced += 1
|
|
||||||
|
|
||||||
|
gevent.joinall(threads) # Wait for announce finish
|
||||||
|
|
||||||
|
for thread in threads:
|
||||||
|
if thread.value:
|
||||||
|
if thread.value > 1:
|
||||||
|
slow.append("%.2fs %s://%s" % (thread.value, thread.protocol, thread.ip))
|
||||||
|
announced += 1
|
||||||
|
else:
|
||||||
|
errors.append("%s://%s" % (thread.protocol, thread.ip))
|
||||||
|
|
||||||
# Save peers num
|
# Save peers num
|
||||||
self.settings["peers"] = len(self.peers)
|
self.settings["peers"] = len(self.peers)
|
||||||
self.saveSettings()
|
self.saveSettings()
|
||||||
|
|
||||||
if len(errors) < len(SiteManager.TRACKERS): # Less errors than total tracker nums
|
if len(errors) < len(SiteManager.TRACKERS): # Less errors than total tracker nums
|
||||||
self.log.debug("Announced port %s to %s trackers in %.3fs, errors: %s" % (fileserver_port, announced, time.time()-s, errors))
|
self.log.debug("Announced port %s to %s trackers in %.3fs, errors: %s, slow: %s" % (fileserver_port, announced, time.time()-s, errors, slow))
|
||||||
else:
|
else:
|
||||||
self.log.error("Announced to %s trackers in %.3fs, failed" % (announced, time.time()-s))
|
self.log.error("Announced to %s trackers in %.3fs, failed" % (announced, time.time()-s))
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,12 @@ TRACKERS = [
|
||||||
#("udp", "trackr.sytes.net", 80),
|
#("udp", "trackr.sytes.net", 80),
|
||||||
#("udp", "tracker4.piratux.com", 6969)
|
#("udp", "tracker4.piratux.com", 6969)
|
||||||
("http", "exodus.desync.com:80/announce", None),
|
("http", "exodus.desync.com:80/announce", None),
|
||||||
("http", "announce.torrentsmd.com:6969/announce", None),
|
("http", "tracker.aletorrenty.pl:2710/announce", None),
|
||||||
#("http", "i.bandito.org/announce", None),
|
#("http", "torrent.gresille.org/announce", None), # Slow
|
||||||
#("http", "tracker.tfile.me/announce", None),
|
#("http", "announce.torrentsmd.com:6969/announce", None), # Off
|
||||||
|
#("http", "i.bandito.org/announce", None), # Off
|
||||||
|
("http", "retracker.telecom.kz/announce", None)
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import os, re, shutil, json, time, sqlite3
|
import os, re, shutil, json, time, sqlite3
|
||||||
import gevent.event
|
import gevent.event
|
||||||
from Db import Db
|
from Db import Db
|
||||||
|
from Debug import Debug
|
||||||
|
|
||||||
|
|
||||||
class SiteStorage:
|
class SiteStorage:
|
||||||
def __init__(self, site, allow_create=True):
|
def __init__(self, site, allow_create=True):
|
||||||
|
@ -36,13 +38,17 @@ class SiteStorage:
|
||||||
|
|
||||||
def closeDb(self):
|
def closeDb(self):
|
||||||
if self.db: self.db.close()
|
if self.db: self.db.close()
|
||||||
|
self.event_db_busy = None
|
||||||
self.db = None
|
self.db = None
|
||||||
|
|
||||||
|
|
||||||
# Return db class
|
# Return db class
|
||||||
def getDb(self):
|
def getDb(self):
|
||||||
if not self.db and self.has_db:
|
if not self.db:
|
||||||
self.openDb()
|
self.log.debug("No database, waiting for dbschema.json...")
|
||||||
|
self.site.needFile("dbschema.json", priority=1)
|
||||||
|
self.has_db = self.isFile("dbschema.json") # Recheck if dbschema exits
|
||||||
|
if self.has_db: self.openDb()
|
||||||
return self.db
|
return self.db
|
||||||
|
|
||||||
|
|
||||||
|
@ -143,10 +149,13 @@ class SiteStorage:
|
||||||
if inner_path == "dbschema.json":
|
if inner_path == "dbschema.json":
|
||||||
self.has_db = self.isFile("dbschema.json")
|
self.has_db = self.isFile("dbschema.json")
|
||||||
self.getDb().checkTables() # Check if any if table schema changed
|
self.getDb().checkTables() # Check if any if table schema changed
|
||||||
elif inner_path != "content.json" and inner_path.endswith(".json") and self.has_db: # Load json file to db
|
elif inner_path.endswith(".json") and self.has_db: # Load json file to db
|
||||||
self.log.debug("Loading json file to db: %s" % inner_path)
|
self.log.debug("Loading json file to db: %s" % inner_path)
|
||||||
self.getDb().loadJson(file_path)
|
try:
|
||||||
|
self.getDb().loadJson(file_path)
|
||||||
|
except Exception, err:
|
||||||
|
self.log.error("Json %s load error: %s" % (inner_path, Debug.formatException(err)))
|
||||||
|
self.closeDb()
|
||||||
|
|
||||||
|
|
||||||
# Load and parse json file
|
# Load and parse json file
|
||||||
|
@ -154,6 +163,21 @@ class SiteStorage:
|
||||||
with self.open(inner_path) as file:
|
with self.open(inner_path) as file:
|
||||||
return json.load(file)
|
return json.load(file)
|
||||||
|
|
||||||
|
# Write formatted json file
|
||||||
|
def writeJson(self, inner_path, data):
|
||||||
|
content = json.dumps(data, indent=2, sort_keys=True)
|
||||||
|
# Make it a little more compact by removing unnecessary white space
|
||||||
|
def compact_list(match):
|
||||||
|
return "[ "+match.group(1).strip()+" ]"
|
||||||
|
|
||||||
|
def compact_dict(match):
|
||||||
|
return "{ "+match.group(1).strip()+" }"
|
||||||
|
|
||||||
|
content = re.sub("\[([^,\{\[]{10,100}?)\]", compact_list, content, flags=re.DOTALL)
|
||||||
|
content = re.sub("\{([^,\[\{]{10,100}?)\}", compact_dict, content, flags=re.DOTALL)
|
||||||
|
# Write to disk
|
||||||
|
self.write(inner_path, content)
|
||||||
|
|
||||||
|
|
||||||
# Get file size
|
# Get file size
|
||||||
def getSize(self, inner_path):
|
def getSize(self, inner_path):
|
||||||
|
|
114
src/Test/test.py
114
src/Test/test.py
|
@ -152,7 +152,6 @@ class TestCase(unittest.TestCase):
|
||||||
# Cleanup
|
# Cleanup
|
||||||
os.unlink("data/test/zeronet.db")
|
os.unlink("data/test/zeronet.db")
|
||||||
os.rmdir("data/test/")
|
os.rmdir("data/test/")
|
||||||
print "ok"
|
|
||||||
|
|
||||||
|
|
||||||
def testContentManagerIncludes(self):
|
def testContentManagerIncludes(self):
|
||||||
|
@ -162,12 +161,12 @@ class TestCase(unittest.TestCase):
|
||||||
|
|
||||||
site = Site("1TaLk3zM7ZRskJvrh3ZNCDVGXvkJusPKQ")
|
site = Site("1TaLk3zM7ZRskJvrh3ZNCDVGXvkJusPKQ")
|
||||||
# Include info
|
# Include info
|
||||||
include_info = site.content_manager.getIncludeInfo("data/users/1BhcaqWViN1YBnNgXb5aq5NtEhKtKdKZMB/content.json")
|
rules = site.content_manager.getRules("data/users/1BhcaqWViN1YBnNgXb5aq5NtEhKtKdKZMB/content.json")
|
||||||
self.assertEqual(include_info["signers"], ['1BhcaqWViN1YBnNgXb5aq5NtEhKtKdKZMB'])
|
self.assertEqual(rules["signers"], ['1BhcaqWViN1YBnNgXb5aq5NtEhKtKdKZMB'])
|
||||||
self.assertEqual(include_info["user_name"], 'testuser4')
|
self.assertEqual(rules["user_name"], 'testuser4')
|
||||||
self.assertEqual(include_info["max_size"], 10000)
|
self.assertEqual(rules["max_size"], 10000)
|
||||||
self.assertEqual(include_info["includes_allowed"], False)
|
self.assertEqual(rules["includes_allowed"], False)
|
||||||
self.assertEqual(include_info["files_allowed"], 'data.json')
|
self.assertEqual(rules["files_allowed"], 'data.json')
|
||||||
# Valid signers
|
# Valid signers
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
site.content_manager.getValidSigners("data/users/1BhcaqWViN1YBnNgXb5aq5NtEhKtKdKZMB/content.json"),
|
site.content_manager.getValidSigners("data/users/1BhcaqWViN1YBnNgXb5aq5NtEhKtKdKZMB/content.json"),
|
||||||
|
@ -207,9 +206,106 @@ class TestCase(unittest.TestCase):
|
||||||
self.assertEqual(site.content_manager.verifyFile("data/users/1BhcaqWViN1YBnNgXb5aq5NtEhKtKdKZMB/content.json", data, ignore_same=False), True)
|
self.assertEqual(site.content_manager.verifyFile("data/users/1BhcaqWViN1YBnNgXb5aq5NtEhKtKdKZMB/content.json", data, ignore_same=False), True)
|
||||||
|
|
||||||
|
|
||||||
|
def testUserContentRules(self):
|
||||||
|
from Site import Site
|
||||||
|
from cStringIO import StringIO
|
||||||
|
import json
|
||||||
|
|
||||||
|
site = Site("1Hb9rY98TNnA6TYeozJv4w36bqEiBn6x8Y")
|
||||||
|
user_content = site.storage.loadJson("data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json")
|
||||||
|
|
||||||
|
# File info for not exits file
|
||||||
|
self.assertEqual(site.content_manager.getFileInfo("data/users/notexits/data.json")["content_inner_path"], "data/users/notexits/content.json")
|
||||||
|
self.assertEqual(site.content_manager.getValidSigners("data/users/notexits/data.json"), ["notexits", "1Hb9rY98TNnA6TYeozJv4w36bqEiBn6x8Y"])
|
||||||
|
|
||||||
|
# File info for exsitsing file
|
||||||
|
file_info = site.content_manager.getFileInfo("data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/data.json")
|
||||||
|
valid_signers = site.content_manager.getValidSigners(file_info["content_inner_path"], user_content)
|
||||||
|
self.assertEqual(valid_signers, ['14wgQ4VDDZNoRMFF4yCDuTrBSHmYhL3bet', '1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C', '1Hb9rY98TNnA6TYeozJv4w36bqEiBn6x8Y'])
|
||||||
|
|
||||||
|
# Known user
|
||||||
|
user_content["cert_auth_type"] = "web"
|
||||||
|
user_content["cert_user_id"] = "nofish@zeroid.bit"
|
||||||
|
rules = site.content_manager.getRules("data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json", user_content)
|
||||||
|
self.assertEqual(rules["max_size"], 100000)
|
||||||
|
|
||||||
|
# Unknown user
|
||||||
|
user_content["cert_auth_type"] = "web"
|
||||||
|
user_content["cert_user_id"] = "noone@zeroid.bit"
|
||||||
|
rules = site.content_manager.getRules("data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json", user_content)
|
||||||
|
self.assertEqual(rules["max_size"], 10000)
|
||||||
|
|
||||||
|
# User with more size limit by auth type
|
||||||
|
user_content["cert_auth_type"] = "bitmsg"
|
||||||
|
user_content["cert_user_id"] = "noone@zeroid.bit"
|
||||||
|
rules = site.content_manager.getRules("data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json", user_content)
|
||||||
|
self.assertEqual(rules["max_size"], 20000)
|
||||||
|
|
||||||
|
# Banned user
|
||||||
|
user_content["cert_auth_type"] = "web"
|
||||||
|
user_content["cert_user_id"] = "bad@zeroid.bit"
|
||||||
|
rules = site.content_manager.getRules("data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json", user_content)
|
||||||
|
self.assertFalse(rules)
|
||||||
|
|
||||||
|
|
||||||
|
def testUserContentCert(self):
|
||||||
|
from Site import Site
|
||||||
|
from cStringIO import StringIO
|
||||||
|
import json
|
||||||
|
user_addr = "1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C"
|
||||||
|
user_priv = "5Kk7FSA63FC2ViKmKLuBxk9gQkaQ5713hKq8LmFAf4cVeXh6K6A"
|
||||||
|
cert_addr = "14wgQ4VDDZNoRMFF4yCDuTrBSHmYhL3bet"
|
||||||
|
cert_priv = "5JusJDSjHaMHwUjDT3o6eQ54pA6poo8La5fAgn1wNc3iK59jxjA"
|
||||||
|
|
||||||
|
site = Site("1Hb9rY98TNnA6TYeozJv4w36bqEiBn6x8Y")
|
||||||
|
#user_content = site.storage.loadJson("data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json")
|
||||||
|
# site.content_manager.contents["data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json"] = user_content # Add to content manager
|
||||||
|
# Check if the user file is loaded
|
||||||
|
self.assertTrue("data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json" in site.content_manager.contents)
|
||||||
|
user_content = site.content_manager.contents["data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json"]
|
||||||
|
cert_content = site.content_manager.contents["data/users/content.json"]
|
||||||
|
# Override cert signer
|
||||||
|
cert_content["user_contents"]["cert_signers"]["zeroid.bit"] = ["14wgQ4VDDZNoRMFF4yCDuTrBSHmYhL3bet", "1iD5ZQJMNXu43w1qLB8sfdHVKppVMduGz"]
|
||||||
|
|
||||||
|
|
||||||
|
# Valid cert providers
|
||||||
|
rules = site.content_manager.getRules("data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json", user_content)
|
||||||
|
self.assertEqual(rules["cert_signers"], {"zeroid.bit": ["14wgQ4VDDZNoRMFF4yCDuTrBSHmYhL3bet", "1iD5ZQJMNXu43w1qLB8sfdHVKppVMduGz"]} )
|
||||||
|
|
||||||
|
# Add cert
|
||||||
|
user_content["cert_sign"] = CryptBitcoin.sign(
|
||||||
|
"1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C#%s/%s" % (user_content["cert_auth_type"], user_content["cert_user_id"].split("@")[0]), cert_priv
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify cert
|
||||||
|
self.assertTrue(site.content_manager.verifyCert("data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json", user_content))
|
||||||
|
self.assertFalse(site.content_manager.verifyCert("data/users/badaddress/content.json", user_content))
|
||||||
|
|
||||||
|
|
||||||
|
# Sign user content
|
||||||
|
#signed_content = site.content_manager.sign("data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json", user_priv, filewrite=False)
|
||||||
|
signed_content = site.storage.loadJson("data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json")
|
||||||
|
|
||||||
|
# Test user cert
|
||||||
|
self.assertTrue(site.content_manager.verifyFile("data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json", StringIO(json.dumps(signed_content)), ignore_same=False))
|
||||||
|
|
||||||
|
# Test banned user
|
||||||
|
site.content_manager.contents["data/users/content.json"]["user_contents"]["permissions"][user_content["cert_user_id"]] = False
|
||||||
|
self.assertFalse(site.content_manager.verifyFile("data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json", StringIO(json.dumps(signed_content)), ignore_same=False))
|
||||||
|
|
||||||
|
# Test invalid cert
|
||||||
|
user_content["cert_sign"] = CryptBitcoin.sign(
|
||||||
|
"badaddress#%s/%s" % (user_content["cert_auth_type"], user_content["cert_user_id"]), cert_priv
|
||||||
|
)
|
||||||
|
signed_content = site.content_manager.sign("data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json", user_priv, filewrite=False)
|
||||||
|
self.assertFalse(site.content_manager.verifyFile("data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json", StringIO(json.dumps(signed_content)), ignore_same=False))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import logging
|
import logging
|
||||||
logging.getLogger().setLevel(level=logging.CRITICAL)
|
logging.getLogger().setLevel(level=logging.FATAL)
|
||||||
unittest.main(verbosity=2, defaultTest="TestCase.testContentManagerIncludes")
|
unittest.main(verbosity=2)
|
||||||
|
#unittest.main(verbosity=2, defaultTest="TestCase.testUserContentCert")
|
||||||
|
|
||||||
|
|
|
@ -170,7 +170,7 @@ class UiRequest(object):
|
||||||
|
|
||||||
if not site: return False
|
if not site: return False
|
||||||
|
|
||||||
extra_headers.append(("X-Frame-Options", "DENY"))
|
#extra_headers.append(("X-Frame-Options", "DENY"))
|
||||||
|
|
||||||
self.sendHeader(extra_headers=extra_headers[:])
|
self.sendHeader(extra_headers=extra_headers[:])
|
||||||
|
|
||||||
|
|
|
@ -96,44 +96,13 @@ class UiWebsocket(object):
|
||||||
permissions = permissions[:]
|
permissions = permissions[:]
|
||||||
permissions.append("ADMIN")
|
permissions.append("ADMIN")
|
||||||
|
|
||||||
|
admin_commands = ("sitePause", "siteResume", "siteDelete", "siteList", "siteSetLimit", "channelJoinAllsite", "serverUpdate", "certSet")
|
||||||
|
|
||||||
if cmd == "response": # It's a response to a command
|
if cmd == "response": # It's a response to a command
|
||||||
return self.actionResponse(req["to"], req["result"])
|
return self.actionResponse(req["to"], req["result"])
|
||||||
elif cmd == "ping":
|
elif cmd in admin_commands and "ADMIN" not in permissions: # Admin commands
|
||||||
func = self.actionPing
|
return self.response(req["id"], "You don't have permission to run %s" % cmd)
|
||||||
elif cmd == "channelJoin":
|
else: # Normal command
|
||||||
func = self.actionChannelJoin
|
|
||||||
elif cmd == "siteInfo":
|
|
||||||
func = self.actionSiteInfo
|
|
||||||
elif cmd == "serverInfo":
|
|
||||||
func = self.actionServerInfo
|
|
||||||
elif cmd == "siteUpdate":
|
|
||||||
func = self.actionSiteUpdate
|
|
||||||
elif cmd == "sitePublish":
|
|
||||||
func = self.actionSitePublish
|
|
||||||
elif cmd == "fileWrite":
|
|
||||||
func = self.actionFileWrite
|
|
||||||
elif cmd == "fileGet":
|
|
||||||
func = self.actionFileGet
|
|
||||||
elif cmd == "fileQuery":
|
|
||||||
func = self.actionFileQuery
|
|
||||||
elif cmd == "dbQuery":
|
|
||||||
func = self.actionDbQuery
|
|
||||||
# Admin commands
|
|
||||||
elif cmd == "sitePause" and "ADMIN" in permissions:
|
|
||||||
func = self.actionSitePause
|
|
||||||
elif cmd == "siteResume" and "ADMIN" in permissions:
|
|
||||||
func = self.actionSiteResume
|
|
||||||
elif cmd == "siteDelete" and "ADMIN" in permissions:
|
|
||||||
func = self.actionSiteDelete
|
|
||||||
elif cmd == "siteList" and "ADMIN" in permissions:
|
|
||||||
func = self.actionSiteList
|
|
||||||
elif cmd == "siteSetLimit" and "ADMIN" in permissions:
|
|
||||||
func = self.actionSiteSetLimit
|
|
||||||
elif cmd == "channelJoinAllsite" and "ADMIN" in permissions:
|
|
||||||
func = self.actionChannelJoinAllsite
|
|
||||||
elif cmd == "serverUpdate" and "ADMIN" in permissions:
|
|
||||||
func = self.actionServerUpdate
|
|
||||||
else:
|
|
||||||
func_name = "action" + cmd[0].upper() + cmd[1:]
|
func_name = "action" + cmd[0].upper() + cmd[1:]
|
||||||
func = getattr(self, func_name, None)
|
func = getattr(self, func_name, None)
|
||||||
if not func: # Unknown command
|
if not func: # Unknown command
|
||||||
|
@ -158,6 +127,7 @@ class UiWebsocket(object):
|
||||||
content["includes"] = len(content.get("includes", {}))
|
content["includes"] = len(content.get("includes", {}))
|
||||||
if "sign" in content: del(content["sign"])
|
if "sign" in content: del(content["sign"])
|
||||||
if "signs" in content: del(content["signs"])
|
if "signs" in content: del(content["signs"])
|
||||||
|
if "signers_sign" in content: del(content["signers_sign"])
|
||||||
|
|
||||||
settings = site.settings.copy()
|
settings = site.settings.copy()
|
||||||
del settings["wrapper_key"] # Dont expose wrapper key
|
del settings["wrapper_key"] # Dont expose wrapper key
|
||||||
|
@ -167,6 +137,7 @@ class UiWebsocket(object):
|
||||||
"auth_key": self.site.settings["auth_key"], # Obsolete, will be removed
|
"auth_key": self.site.settings["auth_key"], # Obsolete, will be removed
|
||||||
"auth_key_sha512": hashlib.sha512(self.site.settings["auth_key"]).hexdigest()[0:64], # Obsolete, will be removed
|
"auth_key_sha512": hashlib.sha512(self.site.settings["auth_key"]).hexdigest()[0:64], # Obsolete, will be removed
|
||||||
"auth_address": self.user.getAuthAddress(site.address, create=create_user),
|
"auth_address": self.user.getAuthAddress(site.address, create=create_user),
|
||||||
|
"cert_user_id": self.user.getCertUserId(site.address),
|
||||||
"address": site.address,
|
"address": site.address,
|
||||||
"settings": settings,
|
"settings": settings,
|
||||||
"content_updated": site.content_updated,
|
"content_updated": site.content_updated,
|
||||||
|
@ -236,8 +207,16 @@ class UiWebsocket(object):
|
||||||
|
|
||||||
def actionSitePublish(self, to, privatekey=None, inner_path="content.json"):
|
def actionSitePublish(self, to, privatekey=None, inner_path="content.json"):
|
||||||
site = self.site
|
site = self.site
|
||||||
|
extend = {} # Extended info for signing
|
||||||
if not inner_path.endswith("content.json"): # Find the content.json first
|
if not inner_path.endswith("content.json"): # Find the content.json first
|
||||||
inner_path = site.content_manager.getFileInfo(inner_path)["content_inner_path"]
|
file_info = site.content_manager.getFileInfo(inner_path)
|
||||||
|
inner_path = file_info["content_inner_path"]
|
||||||
|
if "cert_signers" in file_info: # Its an user dir file
|
||||||
|
cert = self.user.getCert(self.site.address)
|
||||||
|
extend["cert_auth_type"] = cert["auth_type"]
|
||||||
|
extend["cert_user_id"] = self.user.getCertUserId(site.address)
|
||||||
|
extend["cert_sign"] = cert["cert_sign"]
|
||||||
|
|
||||||
|
|
||||||
if not site.settings["own"] and self.user.getAuthAddress(self.site.address) not in self.site.content_manager.getValidSigners(inner_path):
|
if not site.settings["own"] and self.user.getAuthAddress(self.site.address) not in self.site.content_manager.getValidSigners(inner_path):
|
||||||
return self.response(to, "Forbidden, you can only modify your own sites")
|
return self.response(to, "Forbidden, you can only modify your own sites")
|
||||||
|
@ -246,7 +225,7 @@ class UiWebsocket(object):
|
||||||
|
|
||||||
# Signing
|
# Signing
|
||||||
site.content_manager.loadContent(add_bad_files=False) # Reload content.json, ignore errors to make it up-to-date
|
site.content_manager.loadContent(add_bad_files=False) # Reload content.json, ignore errors to make it up-to-date
|
||||||
signed = site.content_manager.sign(inner_path, privatekey) # Sign using private key sent by user
|
signed = site.content_manager.sign(inner_path, privatekey, extend=extend) # Sign using private key sent by user
|
||||||
if signed:
|
if signed:
|
||||||
if inner_path == "content_json": self.cmd("notification", ["done", "Private key correct, content signed!", 5000]) # Display message for 5 sec
|
if inner_path == "content_json": self.cmd("notification", ["done", "Private key correct, content signed!", 5000]) # Display message for 5 sec
|
||||||
else:
|
else:
|
||||||
|
@ -301,7 +280,13 @@ class UiWebsocket(object):
|
||||||
if inner_path.endswith("content.json"):
|
if inner_path.endswith("content.json"):
|
||||||
self.site.content_manager.loadContent(inner_path, add_bad_files=False)
|
self.site.content_manager.loadContent(inner_path, add_bad_files=False)
|
||||||
|
|
||||||
return self.response(to, "ok")
|
self.response(to, "ok")
|
||||||
|
|
||||||
|
# Send sitechanged to other local users
|
||||||
|
for ws in self.site.websockets:
|
||||||
|
if ws != self:
|
||||||
|
ws.event("siteChanged", self.site, {"event": ["file_done", inner_path]})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Find data in json files
|
# Find data in json files
|
||||||
|
@ -314,7 +299,7 @@ class UiWebsocket(object):
|
||||||
|
|
||||||
|
|
||||||
# Sql query
|
# Sql query
|
||||||
def actionDbQuery(self, to, query, params=None):
|
def actionDbQuery(self, to, query, params=None, wait_for=None):
|
||||||
rows = []
|
rows = []
|
||||||
try:
|
try:
|
||||||
res = self.site.storage.query(query, params)
|
res = self.site.storage.query(query, params)
|
||||||
|
@ -327,15 +312,95 @@ class UiWebsocket(object):
|
||||||
|
|
||||||
|
|
||||||
# Return file content
|
# Return file content
|
||||||
def actionFileGet(self, to, inner_path):
|
def actionFileGet(self, to, inner_path, required=True):
|
||||||
try:
|
try:
|
||||||
self.site.needFile(inner_path, priority=1)
|
if required: self.site.needFile(inner_path, priority=1)
|
||||||
body = self.site.storage.read(inner_path)
|
body = self.site.storage.read(inner_path)
|
||||||
except:
|
except:
|
||||||
body = None
|
body = None
|
||||||
return self.response(to, body)
|
return self.response(to, body)
|
||||||
|
|
||||||
|
|
||||||
|
def actionFileRules(self, to, inner_path):
|
||||||
|
rules = self.site.content_manager.getRules(inner_path)
|
||||||
|
if inner_path.endswith("content.json"):
|
||||||
|
content = self.site.content_manager.contents.get(inner_path)
|
||||||
|
if content:
|
||||||
|
rules["current_size"] = len(json.dumps(content)) + sum([file["size"] for file in content["files"].values()])
|
||||||
|
else:
|
||||||
|
rules["current_size"] = 0
|
||||||
|
return self.response(to, rules)
|
||||||
|
|
||||||
|
|
||||||
|
# Add certificate to user
|
||||||
|
def actionCertAdd(self, to, domain, auth_type, auth_user_name, cert):
|
||||||
|
try:
|
||||||
|
res = self.user.addCert(self.user.getAuthAddress(self.site.address), domain, auth_type, auth_user_name, cert)
|
||||||
|
if res == True:
|
||||||
|
self.cmd("notification", ["done", "New certificate added: <b>%s/%s@%s</b>." % (auth_type, auth_user_name, domain)])
|
||||||
|
self.response(to, "ok")
|
||||||
|
else:
|
||||||
|
self.response(to, "Not changed")
|
||||||
|
except Exception, err:
|
||||||
|
self.response(to, {"error": err.message})
|
||||||
|
|
||||||
|
|
||||||
|
# Select certificate for site
|
||||||
|
def actionCertSelect(self, to, accepted_domains=[]):
|
||||||
|
accounts = []
|
||||||
|
accounts.append(["", "Unique to site", ""]) # Default option
|
||||||
|
active = "" # Make it active if no other option found
|
||||||
|
|
||||||
|
# Add my certs
|
||||||
|
auth_address = self.user.getAuthAddress(self.site.address) # Current auth address
|
||||||
|
for domain, cert in self.user.certs.items():
|
||||||
|
if auth_address == cert["auth_address"]:
|
||||||
|
active = domain
|
||||||
|
title = cert["auth_user_name"]+"@"+domain
|
||||||
|
if domain in accepted_domains:
|
||||||
|
accounts.append([domain, title, ""])
|
||||||
|
else:
|
||||||
|
accounts.append([domain, title, "disabled"])
|
||||||
|
|
||||||
|
|
||||||
|
# Render the html
|
||||||
|
body = "<span style='padding-bottom: 5px; display: inline-block'>Select account you want to use in this site:</span>"
|
||||||
|
# Accounts
|
||||||
|
for domain, account, css_class in accounts:
|
||||||
|
if domain == active:
|
||||||
|
css_class += " active" # Currently selected option
|
||||||
|
title = "<b>%s</b> <small>(currently selected)</small>" % account
|
||||||
|
else:
|
||||||
|
title = "<b>%s</b>" % account
|
||||||
|
body += "<a href='#Select+account' class='select select-close cert %s' title='%s'>%s</a>" % (css_class, domain, title)
|
||||||
|
# More avalible providers
|
||||||
|
more_domains = [domain for domain in accepted_domains if domain not in self.user.certs] # Domainains we not displayed yet
|
||||||
|
if more_domains:
|
||||||
|
# body+= "<small style='margin-top: 10px; display: block'>Accepted authorization providers by the site:</small>"
|
||||||
|
body+= "<div style='background-color: #F7F7F7; margin-right: -30px'>"
|
||||||
|
for domain in more_domains:
|
||||||
|
body += "<a href='/%s' onclick='wrapper.gotoSite(this)' target='_blank' class='select'><small style='float: right; margin-right: 40px; margin-top: -1px'>Register »</small>%s</a>" % (domain, domain)
|
||||||
|
body+= "</div>"
|
||||||
|
|
||||||
|
body += """
|
||||||
|
<script>
|
||||||
|
$(".notification .select.cert").on("click", function() {
|
||||||
|
$(".notification .select").removeClass('active')
|
||||||
|
wrapper.ws.cmd('certSet', [this.title])
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Send the notification
|
||||||
|
self.cmd("notification", ["ask", body])
|
||||||
|
|
||||||
|
|
||||||
|
# Set certificate that used for authenticate user for site
|
||||||
|
def actionCertSet(self, to, domain):
|
||||||
|
self.user.setCert(self.site.address, domain)
|
||||||
|
self.site.updateWebsocket(cert_changed=domain)
|
||||||
|
|
||||||
|
|
||||||
# - Admin actions -
|
# - Admin actions -
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,6 @@ class Notifications
|
||||||
|
|
||||||
|
|
||||||
add: (id, type, body, timeout=0) ->
|
add: (id, type, body, timeout=0) ->
|
||||||
@log id, type, body, timeout
|
|
||||||
# Close notifications with same id
|
# Close notifications with same id
|
||||||
for elem in $(".notification-#{id}")
|
for elem in $(".notification-#{id}")
|
||||||
@close $(elem)
|
@close $(elem)
|
||||||
|
@ -55,15 +54,14 @@ class Notifications
|
||||||
elem.animate({"width": width}, 700, "easeInOutCubic")
|
elem.animate({"width": width}, 700, "easeInOutCubic")
|
||||||
$(".body", elem).cssLater("box-shadow", "0px 0px 5px rgba(0,0,0,0.1)", 1000)
|
$(".body", elem).cssLater("box-shadow", "0px 0px 5px rgba(0,0,0,0.1)", 1000)
|
||||||
|
|
||||||
# Close button
|
# Close button or Confirm button
|
||||||
$(".close", elem).on "click", =>
|
$(".close, .button", elem).on "click", =>
|
||||||
@close elem
|
@close elem
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# Close on button click within body (confirm dialog)
|
# Select list
|
||||||
$(".button", elem).on "click", =>
|
$(".select", elem).on "click", =>
|
||||||
@close elem
|
@close elem
|
||||||
return false
|
|
||||||
|
|
||||||
|
|
||||||
close: (elem) ->
|
close: (elem) ->
|
||||||
|
|
|
@ -30,6 +30,12 @@ class Wrapper
|
||||||
if window.location.hash
|
if window.location.hash
|
||||||
src = $("#inner-iframe").attr("src").replace(/#.*/, "")+window.location.hash
|
src = $("#inner-iframe").attr("src").replace(/#.*/, "")+window.location.hash
|
||||||
$("#inner-iframe").attr("src", src)
|
$("#inner-iframe").attr("src", src)
|
||||||
|
|
||||||
|
###setInterval (->
|
||||||
|
console.log document.hasFocus()
|
||||||
|
), 1000###
|
||||||
|
$("#inner-iframe").focus()
|
||||||
|
|
||||||
@
|
@
|
||||||
|
|
||||||
|
|
||||||
|
@ -67,6 +73,10 @@ class Wrapper
|
||||||
if @ws.ws.readyState == 1 and not @wrapperWsInited # If ws already opened
|
if @ws.ws.readyState == 1 and not @wrapperWsInited # If ws already opened
|
||||||
@sendInner {"cmd": "wrapperOpenedWebsocket"}
|
@sendInner {"cmd": "wrapperOpenedWebsocket"}
|
||||||
@wrapperWsInited = true
|
@wrapperWsInited = true
|
||||||
|
else if cmd == "innerLoaded"
|
||||||
|
if window.location.hash
|
||||||
|
$("#inner-iframe")[0].src += window.location.hash # Hash tag
|
||||||
|
@log "Added hash to location", $("#inner-iframe")[0].src
|
||||||
else if cmd == "wrapperNotification" # Display notification
|
else if cmd == "wrapperNotification" # Display notification
|
||||||
@actionNotification(message)
|
@actionNotification(message)
|
||||||
else if cmd == "wrapperConfirm" # Display confirm message
|
else if cmd == "wrapperConfirm" # Display confirm message
|
||||||
|
@ -208,7 +218,6 @@ class Wrapper
|
||||||
@inner_loaded = true
|
@inner_loaded = true
|
||||||
if not @inner_ready then @sendInner {"cmd": "wrapperReady"} # Inner frame loaded before wrapper
|
if not @inner_ready then @sendInner {"cmd": "wrapperReady"} # Inner frame loaded before wrapper
|
||||||
#if not @site_error then @loading.hideScreen() # Hide loading screen
|
#if not @site_error then @loading.hideScreen() # Hide loading screen
|
||||||
if window.location.hash then $("#inner-iframe")[0].src += window.location.hash # Hash tag
|
|
||||||
if @ws.ws.readyState == 1 and not @site_info # Ws opened
|
if @ws.ws.readyState == 1 and not @site_info # Ws opened
|
||||||
@reloadSiteInfo()
|
@reloadSiteInfo()
|
||||||
else if @site_info and @site_info.content?.title?
|
else if @site_info and @site_info.content?.title?
|
||||||
|
@ -313,13 +322,27 @@ class Wrapper
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
isProxyRequest: ->
|
||||||
|
return window.location.pathname == "/"
|
||||||
|
|
||||||
|
|
||||||
|
gotoSite: (elem) =>
|
||||||
|
href = $(elem).attr("href")
|
||||||
|
if @isProxyRequest() # Fix for proxy request
|
||||||
|
$(elem).attr("href", "http://zero#{href}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
log: (args...) ->
|
log: (args...) ->
|
||||||
console.log "[Wrapper]", args...
|
console.log "[Wrapper]", args...
|
||||||
|
|
||||||
|
origin = window.server_url or window.location.origin
|
||||||
|
|
||||||
if window.server_url
|
if origin.indexOf("https:") == 0
|
||||||
ws_url = "ws://#{window.server_url.replace('http://', '')}/Websocket?wrapper_key=#{window.wrapper_key}"
|
proto = { ws: 'wss', http: 'https' }
|
||||||
else
|
else
|
||||||
ws_url = "ws://#{window.location.hostname}:#{window.location.port}/Websocket?wrapper_key=#{window.wrapper_key}"
|
proto = { ws: 'ws', http: 'http' }
|
||||||
|
|
||||||
|
ws_url = proto.ws + ":" + origin.replace(proto.http+":", "") + "/Websocket?wrapper_key=" + window.wrapper_key
|
||||||
|
|
||||||
window.wrapper = new Wrapper(ws_url)
|
window.wrapper = new Wrapper(ws_url)
|
||||||
|
|
|
@ -58,6 +58,16 @@ a { color: black }
|
||||||
.notification small { color: #AAA }
|
.notification small { color: #AAA }
|
||||||
.body-white .notification { box-shadow: 0px 1px 9px rgba(0,0,0,0.1) }
|
.body-white .notification { box-shadow: 0px 1px 9px rgba(0,0,0,0.1) }
|
||||||
|
|
||||||
|
/* Notification select */
|
||||||
|
.notification .select {
|
||||||
|
display: block; padding: 10px; margin-right: -32px; text-decoration: none; border-left: 3px solid #EEE;
|
||||||
|
margin-top: 1px; transition: all 0.3s; color: #666
|
||||||
|
}
|
||||||
|
.notification .select:hover, .notification .select.active { background-color: #007AFF; border-left: 3px solid #5D68FF; color: white; transition: none }
|
||||||
|
.notification .select:active, .notification .select:focus { background-color: #3396FF; color: white; transition: none; border-left-color: #3396FF }
|
||||||
|
.notification .select.disabled { opacity: 0.5; pointer-events: none }
|
||||||
|
.notification .select small { color: inherit; }
|
||||||
|
|
||||||
/* Notification types */
|
/* Notification types */
|
||||||
.notification-ask .notification-icon { background-color: #f39c12; }
|
.notification-ask .notification-icon { background-color: #f39c12; }
|
||||||
.notification-info .notification-icon { font-size: 22px; font-weight: bold; background-color: #2980b9; line-height: 48px }
|
.notification-info .notification-icon { font-size: 22px; font-weight: bold; background-color: #2980b9; line-height: 48px }
|
||||||
|
@ -115,6 +125,10 @@ a { color: black }
|
||||||
box-shadow: 0 0 10px #AF3BFF, 0 0 5px #29d; opacity: 1.0; transform: rotate(3deg) translate(0px, -4px);
|
box-shadow: 0 0 10px #AF3BFF, 0 0 5px #29d; opacity: 1.0; transform: rotate(3deg) translate(0px, -4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Icons */
|
||||||
|
.icon-profile { font-size: 6px; top: 0em; border-radius: 0.7em 0.7em 0 0; background: #FFFFFF; width: 1.5em; height: 0.7em; position: relative; display: inline-block; margin-right: 4px }
|
||||||
|
.icon-profile::before { position: absolute; content: ""; top: -1em; left: 0.38em; width: 0.8em; height: 0.85em; border-radius: 50%; background: #FFFFFF }
|
||||||
|
|
||||||
/* Animations */
|
/* Animations */
|
||||||
|
|
||||||
@keyframes flip {
|
@keyframes flip {
|
||||||
|
@ -130,3 +144,9 @@ a { color: black }
|
||||||
70% { opacity: 0 }
|
70% { opacity: 0 }
|
||||||
100% { opacity: 0 }
|
100% { opacity: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Print styles */
|
||||||
|
@media print {
|
||||||
|
#inner-iframe { position: fixed; }
|
||||||
|
.progressbar, .fixbutton, .notifications, .loadingscreen { visibility: hidden; }
|
||||||
|
}
|
|
@ -63,6 +63,16 @@ a { color: black }
|
||||||
.notification small { color: #AAA }
|
.notification small { color: #AAA }
|
||||||
.body-white .notification { -webkit-box-shadow: 0px 1px 9px rgba(0,0,0,0.1) ; -moz-box-shadow: 0px 1px 9px rgba(0,0,0,0.1) ; -o-box-shadow: 0px 1px 9px rgba(0,0,0,0.1) ; -ms-box-shadow: 0px 1px 9px rgba(0,0,0,0.1) ; box-shadow: 0px 1px 9px rgba(0,0,0,0.1) }
|
.body-white .notification { -webkit-box-shadow: 0px 1px 9px rgba(0,0,0,0.1) ; -moz-box-shadow: 0px 1px 9px rgba(0,0,0,0.1) ; -o-box-shadow: 0px 1px 9px rgba(0,0,0,0.1) ; -ms-box-shadow: 0px 1px 9px rgba(0,0,0,0.1) ; box-shadow: 0px 1px 9px rgba(0,0,0,0.1) }
|
||||||
|
|
||||||
|
/* Notification select */
|
||||||
|
.notification .select {
|
||||||
|
display: block; padding: 10px; margin-right: -32px; text-decoration: none; border-left: 3px solid #EEE;
|
||||||
|
margin-top: 1px; -webkit-transition: all 0.3s; -moz-transition: all 0.3s; -o-transition: all 0.3s; -ms-transition: all 0.3s; transition: all 0.3s ; color: #666
|
||||||
|
}
|
||||||
|
.notification .select:hover, .notification .select.active { background-color: #007AFF; border-left: 3px solid #5D68FF; color: white; -webkit-transition: none ; -moz-transition: none ; -o-transition: none ; -ms-transition: none ; transition: none }
|
||||||
|
.notification .select:active, .notification .select:focus { background-color: #3396FF; color: white; -webkit-transition: none; -moz-transition: none; -o-transition: none; -ms-transition: none; transition: none ; border-left-color: #3396FF }
|
||||||
|
.notification .select.disabled { opacity: 0.5; pointer-events: none }
|
||||||
|
.notification .select small { color: inherit; }
|
||||||
|
|
||||||
/* Notification types */
|
/* Notification types */
|
||||||
.notification-ask .notification-icon { background-color: #f39c12; }
|
.notification-ask .notification-icon { background-color: #f39c12; }
|
||||||
.notification-info .notification-icon { font-size: 22px; font-weight: bold; background-color: #2980b9; line-height: 48px }
|
.notification-info .notification-icon { font-size: 22px; font-weight: bold; background-color: #2980b9; line-height: 48px }
|
||||||
|
@ -120,6 +130,10 @@ a { color: black }
|
||||||
-webkit-box-shadow: 0 0 10px #AF3BFF, 0 0 5px #29d; -moz-box-shadow: 0 0 10px #AF3BFF, 0 0 5px #29d; -o-box-shadow: 0 0 10px #AF3BFF, 0 0 5px #29d; -ms-box-shadow: 0 0 10px #AF3BFF, 0 0 5px #29d; box-shadow: 0 0 10px #AF3BFF, 0 0 5px #29d ; opacity: 1.0; -webkit-transform: rotate(3deg) translate(0px, -4px); -moz-transform: rotate(3deg) translate(0px, -4px); -o-transform: rotate(3deg) translate(0px, -4px); -ms-transform: rotate(3deg) translate(0px, -4px); transform: rotate(3deg) translate(0px, -4px) ;
|
-webkit-box-shadow: 0 0 10px #AF3BFF, 0 0 5px #29d; -moz-box-shadow: 0 0 10px #AF3BFF, 0 0 5px #29d; -o-box-shadow: 0 0 10px #AF3BFF, 0 0 5px #29d; -ms-box-shadow: 0 0 10px #AF3BFF, 0 0 5px #29d; box-shadow: 0 0 10px #AF3BFF, 0 0 5px #29d ; opacity: 1.0; -webkit-transform: rotate(3deg) translate(0px, -4px); -moz-transform: rotate(3deg) translate(0px, -4px); -o-transform: rotate(3deg) translate(0px, -4px); -ms-transform: rotate(3deg) translate(0px, -4px); transform: rotate(3deg) translate(0px, -4px) ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Icons */
|
||||||
|
.icon-profile { font-size: 6px; top: 0em; -webkit-border-radius: 0.7em 0.7em 0 0; -moz-border-radius: 0.7em 0.7em 0 0; -o-border-radius: 0.7em 0.7em 0 0; -ms-border-radius: 0.7em 0.7em 0 0; border-radius: 0.7em 0.7em 0 0 ; background: #FFFFFF; width: 1.5em; height: 0.7em; position: relative; display: inline-block; margin-right: 4px }
|
||||||
|
.icon-profile::before { position: absolute; content: ""; top: -1em; left: 0.38em; width: 0.8em; height: 0.85em; -webkit-border-radius: 50%; -moz-border-radius: 50%; -o-border-radius: 50%; -ms-border-radius: 50%; border-radius: 50% ; background: #FFFFFF }
|
||||||
|
|
||||||
/* Animations */
|
/* Animations */
|
||||||
|
|
||||||
@keyframes flip {
|
@keyframes flip {
|
||||||
|
@ -161,8 +175,9 @@ a { color: black }
|
||||||
100% { opacity: 0 }
|
100% { opacity: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Print styles */
|
/* Print styles */
|
||||||
@media print {
|
@media print {
|
||||||
#inner-iframe { position: fixed; }
|
#inner-iframe { position: fixed; }
|
||||||
.progressbar, .fixbutton, .notifications, .loadingscreen { visibility: hidden; }
|
.progressbar, .fixbutton, .notifications, .loadingscreen { visibility: hidden; }
|
||||||
}
|
}
|
|
@ -595,7 +595,6 @@ jQuery.extend( jQuery.easing,
|
||||||
if (timeout == null) {
|
if (timeout == null) {
|
||||||
timeout = 0;
|
timeout = 0;
|
||||||
}
|
}
|
||||||
this.log(id, type, body, timeout);
|
|
||||||
_ref = $(".notification-" + id);
|
_ref = $(".notification-" + id);
|
||||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||||
elem = _ref[_i];
|
elem = _ref[_i];
|
||||||
|
@ -644,16 +643,15 @@ jQuery.extend( jQuery.easing,
|
||||||
"width": width
|
"width": width
|
||||||
}, 700, "easeInOutCubic");
|
}, 700, "easeInOutCubic");
|
||||||
$(".body", elem).cssLater("box-shadow", "0px 0px 5px rgba(0,0,0,0.1)", 1000);
|
$(".body", elem).cssLater("box-shadow", "0px 0px 5px rgba(0,0,0,0.1)", 1000);
|
||||||
$(".close", elem).on("click", (function(_this) {
|
$(".close, .button", elem).on("click", (function(_this) {
|
||||||
return function() {
|
return function() {
|
||||||
_this.close(elem);
|
_this.close(elem);
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
})(this));
|
})(this));
|
||||||
return $(".button", elem).on("click", (function(_this) {
|
return $(".select", elem).on("click", (function(_this) {
|
||||||
return function() {
|
return function() {
|
||||||
_this.close(elem);
|
return _this.close(elem);
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
})(this));
|
})(this));
|
||||||
};
|
};
|
||||||
|
@ -683,6 +681,7 @@ jQuery.extend( jQuery.easing,
|
||||||
}).call(this);
|
}).call(this);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* ---- src/Ui/media/Sidebar.coffee ---- */
|
/* ---- src/Ui/media/Sidebar.coffee ---- */
|
||||||
|
|
||||||
|
|
||||||
|
@ -742,12 +741,13 @@ jQuery.extend( jQuery.easing,
|
||||||
|
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
var Wrapper, ws_url,
|
var Wrapper, origin, proto, ws_url,
|
||||||
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
||||||
__slice = [].slice;
|
__slice = [].slice;
|
||||||
|
|
||||||
Wrapper = (function() {
|
Wrapper = (function() {
|
||||||
function Wrapper(ws_url) {
|
function Wrapper(ws_url) {
|
||||||
|
this.gotoSite = __bind(this.gotoSite, this);
|
||||||
this.setSizeLimit = __bind(this.setSizeLimit, this);
|
this.setSizeLimit = __bind(this.setSizeLimit, this);
|
||||||
this.onLoad = __bind(this.onLoad, this);
|
this.onLoad = __bind(this.onLoad, this);
|
||||||
this.onCloseWebsocket = __bind(this.onCloseWebsocket, this);
|
this.onCloseWebsocket = __bind(this.onCloseWebsocket, this);
|
||||||
|
@ -785,6 +785,12 @@ jQuery.extend( jQuery.easing,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})(this));
|
})(this));
|
||||||
|
|
||||||
|
/*setInterval (->
|
||||||
|
console.log document.hasFocus()
|
||||||
|
), 1000
|
||||||
|
*/
|
||||||
|
$("#inner-iframe").focus();
|
||||||
this;
|
this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -831,6 +837,11 @@ jQuery.extend( jQuery.easing,
|
||||||
});
|
});
|
||||||
return this.wrapperWsInited = true;
|
return this.wrapperWsInited = true;
|
||||||
}
|
}
|
||||||
|
} else if (cmd === "innerLoaded") {
|
||||||
|
if (window.location.hash) {
|
||||||
|
$("#inner-iframe")[0].src += window.location.hash;
|
||||||
|
return this.log("Added hash to location", $("#inner-iframe")[0].src);
|
||||||
|
}
|
||||||
} else if (cmd === "wrapperNotification") {
|
} else if (cmd === "wrapperNotification") {
|
||||||
return this.actionNotification(message);
|
return this.actionNotification(message);
|
||||||
} else if (cmd === "wrapperConfirm") {
|
} else if (cmd === "wrapperConfirm") {
|
||||||
|
@ -1032,9 +1043,6 @@ jQuery.extend( jQuery.easing,
|
||||||
"cmd": "wrapperReady"
|
"cmd": "wrapperReady"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (window.location.hash) {
|
|
||||||
$("#inner-iframe")[0].src += window.location.hash;
|
|
||||||
}
|
|
||||||
if (this.ws.ws.readyState === 1 && !this.site_info) {
|
if (this.ws.ws.readyState === 1 && !this.site_info) {
|
||||||
return this.reloadSiteInfo();
|
return this.reloadSiteInfo();
|
||||||
} else if (this.site_info && (((_ref = this.site_info.content) != null ? _ref.title : void 0) != null)) {
|
} else if (this.site_info && (((_ref = this.site_info.content) != null ? _ref.title : void 0) != null)) {
|
||||||
|
@ -1163,6 +1171,18 @@ jQuery.extend( jQuery.easing,
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Wrapper.prototype.isProxyRequest = function() {
|
||||||
|
return window.location.pathname === "/";
|
||||||
|
};
|
||||||
|
|
||||||
|
Wrapper.prototype.gotoSite = function(elem) {
|
||||||
|
var href;
|
||||||
|
href = $(elem).attr("href");
|
||||||
|
if (this.isProxyRequest()) {
|
||||||
|
return $(elem).attr("href", "http://zero" + href);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Wrapper.prototype.log = function() {
|
Wrapper.prototype.log = function() {
|
||||||
var args;
|
var args;
|
||||||
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
||||||
|
@ -1173,15 +1193,22 @@ jQuery.extend( jQuery.easing,
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
var origin = window.server_url || window.location.origin;
|
origin = window.server_url || window.location.origin;
|
||||||
var proto;
|
|
||||||
if (origin.indexOf('https:') === 0) {
|
if (origin.indexOf("https:") === 0) {
|
||||||
proto = { ws: 'wss', ht: 'https' };
|
proto = {
|
||||||
|
ws: 'wss',
|
||||||
|
http: 'https'
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
proto = { ws: 'ws', ht: 'http' };
|
proto = {
|
||||||
|
ws: 'ws',
|
||||||
|
http: 'http'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
ws_url = proto.ws + ":" + (origin.replace(proto.ht + ':', '')) + "/Websocket?wrapper_key=" + window.wrapper_key;
|
|
||||||
|
ws_url = proto.ws + ":" + origin.replace(proto.http + ":", "") + "/Websocket?wrapper_key=" + window.wrapper_key;
|
||||||
|
|
||||||
window.wrapper = new Wrapper(ws_url);
|
window.wrapper = new Wrapper(ws_url);
|
||||||
|
|
||||||
}).call(this);
|
}).call(this);
|
|
@ -11,6 +11,12 @@
|
||||||
</head>
|
</head>
|
||||||
<body style="{body_style}">
|
<body style="{body_style}">
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// If we are inside iframe escape from it
|
||||||
|
if (window.self !== window.top) window.open(window.location.toString(), "_top");
|
||||||
|
if (window.self !== window.top) window.stop();
|
||||||
|
</script>
|
||||||
|
|
||||||
<div class="progressbar">
|
<div class="progressbar">
|
||||||
<div class="peg"></div>
|
<div class="peg"></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,15 +46,17 @@
|
||||||
|
|
||||||
|
|
||||||
<!-- Site Iframe -->
|
<!-- Site Iframe -->
|
||||||
<iframe src='{inner_path}{query_string}' id='inner-iframe' sandbox="allow-forms allow-scripts allow-top-navigation"></iframe>
|
<iframe src='{inner_path}{query_string}' id='inner-iframe' sandbox="allow-forms allow-scripts allow-top-navigation allow-popups"></iframe>
|
||||||
|
|
||||||
<!-- Site info -->
|
<!-- Site info -->
|
||||||
<script>address = "{address}"</script>
|
<script>
|
||||||
<script>wrapper_key = "{wrapper_key}"</script>
|
address = "{address}"
|
||||||
<script>file_inner_path = "{file_inner_path}"</script>
|
wrapper_key = "{wrapper_key}"
|
||||||
<script>permissions = {permissions}</script>
|
file_inner_path = "{file_inner_path}"
|
||||||
<script>show_loadingscreen = {show_loadingscreen}</script>
|
permissions = {permissions}
|
||||||
<script>server_url = '{server_url}'</script>
|
show_loadingscreen = {show_loadingscreen}
|
||||||
|
server_url = '{server_url}'
|
||||||
|
</script>
|
||||||
<script type="text/javascript" src="{server_url}/uimedia/all.js" asyc></script>
|
<script type="text/javascript" src="{server_url}/uimedia/all.js" asyc></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -4,17 +4,19 @@ from Plugin import PluginManager
|
||||||
|
|
||||||
@PluginManager.acceptPlugins
|
@PluginManager.acceptPlugins
|
||||||
class User(object):
|
class User(object):
|
||||||
def __init__(self, master_address=None, master_seed=None):
|
def __init__(self, master_address=None, master_seed=None, data={}):
|
||||||
if master_seed:
|
if master_seed:
|
||||||
self.master_seed = master_seed
|
self.master_seed = master_seed
|
||||||
self.master_address = CryptBitcoin.privatekeyToAddress(self.master_seed)
|
self.master_address = CryptBitcoin.privatekeyToAddress(self.master_seed)
|
||||||
elif master_address:
|
elif master_address:
|
||||||
self.master_address = master_address
|
self.master_address = master_address
|
||||||
self.master_seed = None
|
self.master_seed = data.get("master_seed")
|
||||||
else:
|
else:
|
||||||
self.master_seed = CryptBitcoin.newSeed()
|
self.master_seed = CryptBitcoin.newSeed()
|
||||||
self.master_address = CryptBitcoin.privatekeyToAddress(self.master_seed)
|
self.master_address = CryptBitcoin.privatekeyToAddress(self.master_seed)
|
||||||
self.sites = {}
|
self.sites = data.get("sites", {})
|
||||||
|
self.certs = data.get("certs", {})
|
||||||
|
|
||||||
self.log = logging.getLogger("User:%s" % self.master_address)
|
self.log = logging.getLogger("User:%s" % self.master_address)
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,10 +24,10 @@ class User(object):
|
||||||
def save(self):
|
def save(self):
|
||||||
users = json.load(open("data/users.json"))
|
users = json.load(open("data/users.json"))
|
||||||
if not self.master_address in users: users[self.master_address] = {} # Create if not exits
|
if not self.master_address in users: users[self.master_address] = {} # Create if not exits
|
||||||
|
|
||||||
user_data = users[self.master_address]
|
user_data = users[self.master_address]
|
||||||
if self.master_seed: user_data["master_seed"] = self.master_seed
|
if self.master_seed: user_data["master_seed"] = self.master_seed
|
||||||
user_data["sites"] = self.sites
|
user_data["sites"] = self.sites
|
||||||
|
user_data["certs"] = self.certs
|
||||||
open("data/users.json", "w").write(json.dumps(users, indent=2, sort_keys=True))
|
open("data/users.json", "w").write(json.dumps(users, indent=2, sort_keys=True))
|
||||||
self.log.debug("Saved")
|
self.log.debug("Saved")
|
||||||
|
|
||||||
|
@ -50,14 +52,66 @@ class User(object):
|
||||||
# Get BIP32 address from site address
|
# Get BIP32 address from site address
|
||||||
# Return: BIP32 auth address
|
# Return: BIP32 auth address
|
||||||
def getAuthAddress(self, address, create=True):
|
def getAuthAddress(self, address, create=True):
|
||||||
return self.getSiteData(address, create)["auth_address"]
|
cert = self.getCert(address)
|
||||||
|
if cert:
|
||||||
|
return cert["auth_address"]
|
||||||
|
else:
|
||||||
|
return self.getSiteData(address, create)["auth_address"]
|
||||||
|
|
||||||
|
|
||||||
def getAuthPrivatekey(self, address, create=True):
|
def getAuthPrivatekey(self, address, create=True):
|
||||||
return self.getSiteData(address, create)["auth_privatekey"]
|
cert = self.getCert(address)
|
||||||
|
if cert:
|
||||||
|
return cert["auth_privatekey"]
|
||||||
|
else:
|
||||||
|
return self.getSiteData(address, create)["auth_privatekey"]
|
||||||
|
|
||||||
|
|
||||||
# Set user attributes from dict
|
# Add cert for the user
|
||||||
def setData(self, data):
|
def addCert(self, auth_address, domain, auth_type, auth_user_name, cert_sign):
|
||||||
for key, val in data.items():
|
domain = domain.lower()
|
||||||
setattr(self, key, val)
|
auth_privatekey = [site["auth_privatekey"] for site in self.sites.values() if site["auth_address"] == auth_address][0] # Find privatekey by auth address
|
||||||
|
cert_node = {
|
||||||
|
"auth_address": auth_address,
|
||||||
|
"auth_privatekey": auth_privatekey,
|
||||||
|
"auth_type": auth_type,
|
||||||
|
"auth_user_name": auth_user_name,
|
||||||
|
"cert_sign": cert_sign
|
||||||
|
}
|
||||||
|
# Check if we have already cert for that domain and its not the same
|
||||||
|
if self.certs.get(domain) and self.certs[domain] != cert_node:
|
||||||
|
raise Exception("You already have certificate for this domain: %s/%s@%s" % (self.certs[domain]["auth_type"], self.certs[domain]["auth_user_name"], domain))
|
||||||
|
elif self.certs.get(domain) == cert_node: # Same, not updated
|
||||||
|
return None
|
||||||
|
else: # Not exits yet, add
|
||||||
|
self.certs[domain] = cert_node
|
||||||
|
self.save()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def setCert(self, address, domain):
|
||||||
|
site_data = self.getSiteData(address)
|
||||||
|
if domain:
|
||||||
|
site_data["cert"] = domain
|
||||||
|
else:
|
||||||
|
del site_data["cert"]
|
||||||
|
self.save()
|
||||||
|
return site_data
|
||||||
|
|
||||||
|
|
||||||
|
# Get cert for the site address
|
||||||
|
# Return: { "auth_address": ..., "auth_privatekey":..., "auth_type": "web", "auth_user_name": "nofish", "cert_sign": ... } or None
|
||||||
|
def getCert(self, address):
|
||||||
|
site_data = self.getSiteData(address, create=False)
|
||||||
|
if not site_data or not "cert" in site_data: return None # Site dont have cert
|
||||||
|
return self.certs.get(site_data["cert"])
|
||||||
|
|
||||||
|
|
||||||
|
# Get cert user name for the site address
|
||||||
|
# Return: user@certprovider.bit or None
|
||||||
|
def getCertUserId(self, address):
|
||||||
|
site_data = self.getSiteData(address, create=False)
|
||||||
|
if not site_data or not "cert" in site_data: return None # Site dont have cert
|
||||||
|
cert = self.certs.get(site_data["cert"])
|
||||||
|
if cert:
|
||||||
|
return cert["auth_user_name"]+"@"+site_data["cert"]
|
|
@ -18,8 +18,7 @@ class UserManager(object):
|
||||||
# Load new users
|
# Load new users
|
||||||
for master_address, data in json.load(open("data/users.json")).items():
|
for master_address, data in json.load(open("data/users.json")).items():
|
||||||
if master_address not in self.users:
|
if master_address not in self.users:
|
||||||
user = User(master_address)
|
user = User(master_address, data=data)
|
||||||
user.setData(data)
|
|
||||||
self.users[master_address] = user
|
self.users[master_address] = user
|
||||||
added += 1
|
added += 1
|
||||||
user_found.append(master_address)
|
user_found.append(master_address)
|
||||||
|
|
|
@ -62,7 +62,7 @@ class Worker:
|
||||||
task["failed"].append(self.peer)
|
task["failed"].append(self.peer)
|
||||||
self.task = None
|
self.task = None
|
||||||
self.peer.hash_failed += 1
|
self.peer.hash_failed += 1
|
||||||
if self.peer.hash_failed >= 3: # Broken peer
|
if self.peer.hash_failed >= max(len(self.manager.tasks), 3): # More fails than tasks number but atleast 3: Broken peer
|
||||||
break
|
break
|
||||||
task["workers_num"] -= 1
|
task["workers_num"] -= 1
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
@ -77,9 +77,17 @@ class Worker:
|
||||||
self.thread = gevent.spawn(self.downloader)
|
self.thread = gevent.spawn(self.downloader)
|
||||||
|
|
||||||
|
|
||||||
|
# Skip current task
|
||||||
|
def skip(self):
|
||||||
|
self.manager.log.debug("%s: Force skipping" % self.key)
|
||||||
|
if self.thread:
|
||||||
|
self.thread.kill(exception=Debug.Notify("Worker stopped"))
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
|
||||||
# Force stop the worker
|
# Force stop the worker
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.manager.log.debug("%s: Force stopping, thread" % self.key)
|
self.manager.log.debug("%s: Force stopping" % self.key)
|
||||||
self.running = False
|
self.running = False
|
||||||
if self.thread:
|
if self.thread:
|
||||||
self.thread.kill(exception=Debug.Notify("Worker stopped"))
|
self.thread.kill(exception=Debug.Notify("Worker stopped"))
|
||||||
|
|
|
@ -32,18 +32,23 @@ class WorkerManager:
|
||||||
|
|
||||||
# Clean up workers
|
# Clean up workers
|
||||||
for worker in self.workers.values():
|
for worker in self.workers.values():
|
||||||
if worker.task and worker.task["done"]: worker.stop() # Stop workers with task done
|
if worker.task and worker.task["done"]: worker.skip() # Stop workers with task done
|
||||||
|
|
||||||
if not self.tasks: continue
|
if not self.tasks: continue
|
||||||
|
|
||||||
tasks = self.tasks[:] # Copy it so removing elements wont cause any problem
|
tasks = self.tasks[:] # Copy it so removing elements wont cause any problem
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
if (task["time_started"] and time.time() >= task["time_started"]+60) or (time.time() >= task["time_added"]+60 and not self.workers): # Task taking too long time, or no peer after 60sec kill it
|
if task["time_started"] and time.time() >= task["time_started"]+60: # Task taking too long time, skip it
|
||||||
self.log.debug("Timeout, Cleaning up task: %s" % task)
|
self.log.debug("Timeout, Skipping: %s" % task)
|
||||||
# Clean up workers
|
# Skip to next file workers
|
||||||
workers = self.findWorkers(task)
|
workers = self.findWorkers(task)
|
||||||
for worker in workers:
|
if workers:
|
||||||
worker.stop()
|
for worker in workers:
|
||||||
|
worker.skip()
|
||||||
|
else:
|
||||||
|
self.failTask(task)
|
||||||
|
elif time.time() >= task["time_added"]+60 and not self.workers: # No workers left
|
||||||
|
self.log.debug("Timeout, Cleanup task: %s" % task)
|
||||||
# Remove task
|
# Remove task
|
||||||
self.failTask(task)
|
self.failTask(task)
|
||||||
|
|
||||||
|
@ -178,12 +183,13 @@ class WorkerManager:
|
||||||
|
|
||||||
# Mark a task failed
|
# Mark a task failed
|
||||||
def failTask(self, task):
|
def failTask(self, task):
|
||||||
task["done"] = True
|
if task in self.tasks:
|
||||||
self.tasks.remove(task) # Remove from queue
|
task["done"] = True
|
||||||
self.site.onFileFail(task["inner_path"])
|
self.tasks.remove(task) # Remove from queue
|
||||||
task["evt"].set(False)
|
self.site.onFileFail(task["inner_path"])
|
||||||
if not self.tasks:
|
task["evt"].set(False)
|
||||||
self.started_task_num = 0
|
if not self.tasks:
|
||||||
|
self.started_task_num = 0
|
||||||
|
|
||||||
|
|
||||||
# Mark a task done
|
# Mark a task done
|
||||||
|
|
393
src/lib/opensslVerify/opensslVerify-alter.py
Normal file
393
src/lib/opensslVerify/opensslVerify-alter.py
Normal file
|
@ -0,0 +1,393 @@
|
||||||
|
# Code is borrowed from https://github.com/blocktrail/python-bitcoinlib
|
||||||
|
# Thanks!
|
||||||
|
|
||||||
|
import base64, hashlib
|
||||||
|
|
||||||
|
import ctypes
|
||||||
|
import ctypes.util
|
||||||
|
_bchr = chr
|
||||||
|
_bord = ord
|
||||||
|
try:
|
||||||
|
_ssl = ctypes.CDLL("src/lib/opensslVerify/libeay32.dll")
|
||||||
|
except:
|
||||||
|
_ssl = ctypes.cdll.LoadLibrary(ctypes.util.find_library('ssl') or ctypes.util.find_library('crypto') or 'libeay32')
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
openssl_version = "%.9X" % _ssl.SSLeay()
|
||||||
|
|
||||||
|
|
||||||
|
# this specifies the curve used with ECDSA.
|
||||||
|
_NID_secp256k1 = 714 # from openssl/obj_mac.h
|
||||||
|
|
||||||
|
# Thx to Sam Devlin for the ctypes magic 64-bit fix.
|
||||||
|
def _check_result (val, func, args):
|
||||||
|
if val == 0:
|
||||||
|
raise ValueError
|
||||||
|
else:
|
||||||
|
return ctypes.c_void_p(val)
|
||||||
|
|
||||||
|
_ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p
|
||||||
|
_ssl.EC_KEY_new_by_curve_name.errcheck = _check_result
|
||||||
|
|
||||||
|
# From openssl/ecdsa.h
|
||||||
|
class ECDSA_SIG_st(ctypes.Structure):
|
||||||
|
_fields_ = [("r", ctypes.c_void_p),
|
||||||
|
("s", ctypes.c_void_p)]
|
||||||
|
|
||||||
|
class CECKey:
|
||||||
|
"""Wrapper around OpenSSL's EC_KEY"""
|
||||||
|
|
||||||
|
POINT_CONVERSION_COMPRESSED = 2
|
||||||
|
POINT_CONVERSION_UNCOMPRESSED = 4
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.k = _ssl.EC_KEY_new_by_curve_name(_NID_secp256k1)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
if _ssl:
|
||||||
|
_ssl.EC_KEY_free(self.k)
|
||||||
|
self.k = None
|
||||||
|
|
||||||
|
def set_secretbytes(self, secret):
|
||||||
|
priv_key = _ssl.BN_bin2bn(secret, 32, _ssl.BN_new())
|
||||||
|
group = _ssl.EC_KEY_get0_group(self.k)
|
||||||
|
pub_key = _ssl.EC_POINT_new(group)
|
||||||
|
ctx = _ssl.BN_CTX_new()
|
||||||
|
if not _ssl.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx):
|
||||||
|
raise ValueError("Could not derive public key from the supplied secret.")
|
||||||
|
_ssl.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx)
|
||||||
|
_ssl.EC_KEY_set_private_key(self.k, priv_key)
|
||||||
|
_ssl.EC_KEY_set_public_key(self.k, pub_key)
|
||||||
|
_ssl.EC_POINT_free(pub_key)
|
||||||
|
_ssl.BN_CTX_free(ctx)
|
||||||
|
return self.k
|
||||||
|
|
||||||
|
def set_privkey(self, key):
|
||||||
|
self.mb = ctypes.create_string_buffer(key)
|
||||||
|
return _ssl.d2i_ECPrivateKey(ctypes.byref(self.k), ctypes.byref(ctypes.pointer(self.mb)), len(key))
|
||||||
|
|
||||||
|
def set_pubkey(self, key):
|
||||||
|
self.mb = ctypes.create_string_buffer(key)
|
||||||
|
return _ssl.o2i_ECPublicKey(ctypes.byref(self.k), ctypes.byref(ctypes.pointer(self.mb)), len(key))
|
||||||
|
|
||||||
|
def get_privkey(self):
|
||||||
|
size = _ssl.i2d_ECPrivateKey(self.k, 0)
|
||||||
|
mb_pri = ctypes.create_string_buffer(size)
|
||||||
|
_ssl.i2d_ECPrivateKey(self.k, ctypes.byref(ctypes.pointer(mb_pri)))
|
||||||
|
return mb_pri.raw
|
||||||
|
|
||||||
|
def get_pubkey(self):
|
||||||
|
size = _ssl.i2o_ECPublicKey(self.k, 0)
|
||||||
|
mb = ctypes.create_string_buffer(size)
|
||||||
|
_ssl.i2o_ECPublicKey(self.k, ctypes.byref(ctypes.pointer(mb)))
|
||||||
|
return mb.raw
|
||||||
|
|
||||||
|
def get_raw_ecdh_key(self, other_pubkey):
|
||||||
|
ecdh_keybuffer = ctypes.create_string_buffer(32)
|
||||||
|
r = _ssl.ECDH_compute_key(ctypes.pointer(ecdh_keybuffer), 32,
|
||||||
|
_ssl.EC_KEY_get0_public_key(other_pubkey.k),
|
||||||
|
self.k, 0)
|
||||||
|
if r != 32:
|
||||||
|
raise Exception('CKey.get_ecdh_key(): ECDH_compute_key() failed')
|
||||||
|
return ecdh_keybuffer.raw
|
||||||
|
|
||||||
|
def get_ecdh_key(self, other_pubkey, kdf=lambda k: hashlib.sha256(k).digest()):
|
||||||
|
# FIXME: be warned it's not clear what the kdf should be as a default
|
||||||
|
r = self.get_raw_ecdh_key(other_pubkey)
|
||||||
|
return kdf(r)
|
||||||
|
|
||||||
|
def sign(self, hash):
|
||||||
|
if not isinstance(hash, bytes):
|
||||||
|
raise TypeError('Hash must be bytes instance; got %r' % hash.__class__)
|
||||||
|
if len(hash) != 32:
|
||||||
|
raise ValueError('Hash must be exactly 32 bytes long')
|
||||||
|
|
||||||
|
sig_size0 = ctypes.c_uint32()
|
||||||
|
sig_size0.value = _ssl.ECDSA_size(self.k)
|
||||||
|
mb_sig = ctypes.create_string_buffer(sig_size0.value)
|
||||||
|
result = _ssl.ECDSA_sign(0, hash, len(hash), mb_sig, ctypes.byref(sig_size0), self.k)
|
||||||
|
assert 1 == result
|
||||||
|
if bitcoin.core.script.IsLowDERSignature(mb_sig.raw[:sig_size0.value]):
|
||||||
|
return mb_sig.raw[:sig_size0.value]
|
||||||
|
else:
|
||||||
|
return self.signature_to_low_s(mb_sig.raw[:sig_size0.value])
|
||||||
|
|
||||||
|
def sign_compact(self, hash):
|
||||||
|
if not isinstance(hash, bytes):
|
||||||
|
raise TypeError('Hash must be bytes instance; got %r' % hash.__class__)
|
||||||
|
if len(hash) != 32:
|
||||||
|
raise ValueError('Hash must be exactly 32 bytes long')
|
||||||
|
|
||||||
|
sig_size0 = ctypes.c_uint32()
|
||||||
|
sig_size0.value = _ssl.ECDSA_size(self.k)
|
||||||
|
mb_sig = ctypes.create_string_buffer(sig_size0.value)
|
||||||
|
result = _ssl.ECDSA_sign(0, hash, len(hash), mb_sig, ctypes.byref(sig_size0), self.k)
|
||||||
|
assert 1 == result
|
||||||
|
|
||||||
|
if bitcoin.core.script.IsLowDERSignature(mb_sig.raw[:sig_size0.value]):
|
||||||
|
sig = mb_sig.raw[:sig_size0.value]
|
||||||
|
else:
|
||||||
|
sig = self.signature_to_low_s(mb_sig.raw[:sig_size0.value])
|
||||||
|
|
||||||
|
sig = bitcoin.core.DERSignature.deserialize(sig)
|
||||||
|
|
||||||
|
r_val = sig.r
|
||||||
|
s_val = sig.s
|
||||||
|
|
||||||
|
# assert that the r and s are less than 32 long, excluding leading 0s
|
||||||
|
assert len(r_val) <= 32 or r_val[0:-32] == b'\x00'
|
||||||
|
assert len(s_val) <= 32 or s_val[0:-32] == b'\x00'
|
||||||
|
|
||||||
|
# ensure r and s are always 32 chars long by 0padding
|
||||||
|
r_val = ((b'\x00' * 32) + r_val)[-32:]
|
||||||
|
s_val = ((b'\x00' * 32) + s_val)[-32:]
|
||||||
|
|
||||||
|
# tmp pubkey of self, but always compressed
|
||||||
|
pubkey = CECKey()
|
||||||
|
pubkey.set_pubkey(self.get_pubkey())
|
||||||
|
pubkey.set_compressed(True)
|
||||||
|
|
||||||
|
# bitcoin core does <4, but I've seen other places do <2 and I've never seen a i > 1 so far
|
||||||
|
for i in range(0, 4):
|
||||||
|
cec_key = CECKey()
|
||||||
|
cec_key.set_compressed(True)
|
||||||
|
|
||||||
|
result = cec_key.recover(r_val, s_val, hash, len(hash), i, 1)
|
||||||
|
if result == 1:
|
||||||
|
if cec_key.get_pubkey() == pubkey.get_pubkey():
|
||||||
|
return r_val + s_val, i
|
||||||
|
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
def signature_to_low_s(self, sig):
|
||||||
|
der_sig = ECDSA_SIG_st()
|
||||||
|
_ssl.d2i_ECDSA_SIG(ctypes.byref(ctypes.pointer(der_sig)), ctypes.byref(ctypes.c_char_p(sig)), len(sig))
|
||||||
|
group = _ssl.EC_KEY_get0_group(self.k)
|
||||||
|
order = _ssl.BN_new()
|
||||||
|
halforder = _ssl.BN_new()
|
||||||
|
ctx = _ssl.BN_CTX_new()
|
||||||
|
_ssl.EC_GROUP_get_order(group, order, ctx)
|
||||||
|
_ssl.BN_rshift1(halforder, order)
|
||||||
|
|
||||||
|
# Verify that s is over half the order of the curve before we actually subtract anything from it
|
||||||
|
if _ssl.BN_cmp(der_sig.s, halforder) > 0:
|
||||||
|
_ssl.BN_sub(der_sig.s, order, der_sig.s)
|
||||||
|
|
||||||
|
_ssl.BN_free(halforder)
|
||||||
|
_ssl.BN_free(order)
|
||||||
|
_ssl.BN_CTX_free(ctx)
|
||||||
|
|
||||||
|
derlen = _ssl.i2d_ECDSA_SIG(ctypes.pointer(der_sig), 0)
|
||||||
|
if derlen == 0:
|
||||||
|
_ssl.ECDSA_SIG_free(der_sig)
|
||||||
|
return None
|
||||||
|
new_sig = ctypes.create_string_buffer(derlen)
|
||||||
|
_ssl.i2d_ECDSA_SIG(ctypes.pointer(der_sig), ctypes.byref(ctypes.pointer(new_sig)))
|
||||||
|
_ssl.BN_free(der_sig.r)
|
||||||
|
_ssl.BN_free(der_sig.s)
|
||||||
|
|
||||||
|
return new_sig.raw
|
||||||
|
|
||||||
|
def verify(self, hash, sig):
|
||||||
|
"""Verify a DER signature"""
|
||||||
|
if not sig:
|
||||||
|
return false
|
||||||
|
|
||||||
|
# New versions of OpenSSL will reject non-canonical DER signatures. de/re-serialize first.
|
||||||
|
norm_sig = ctypes.c_void_p(0)
|
||||||
|
_ssl.d2i_ECDSA_SIG(ctypes.byref(norm_sig), ctypes.byref(ctypes.c_char_p(sig)), len(sig))
|
||||||
|
|
||||||
|
derlen = _ssl.i2d_ECDSA_SIG(norm_sig, 0)
|
||||||
|
if derlen == 0:
|
||||||
|
_ssl.ECDSA_SIG_free(norm_sig)
|
||||||
|
return false
|
||||||
|
|
||||||
|
norm_der = ctypes.create_string_buffer(derlen)
|
||||||
|
_ssl.i2d_ECDSA_SIG(norm_sig, ctypes.byref(ctypes.pointer(norm_der)))
|
||||||
|
_ssl.ECDSA_SIG_free(norm_sig)
|
||||||
|
|
||||||
|
# -1 = error, 0 = bad sig, 1 = good
|
||||||
|
return _ssl.ECDSA_verify(0, hash, len(hash), norm_der, derlen, self.k) == 1
|
||||||
|
|
||||||
|
def set_compressed(self, compressed):
|
||||||
|
if compressed:
|
||||||
|
form = self.POINT_CONVERSION_COMPRESSED
|
||||||
|
else:
|
||||||
|
form = self.POINT_CONVERSION_UNCOMPRESSED
|
||||||
|
_ssl.EC_KEY_set_conv_form(self.k, form)
|
||||||
|
|
||||||
|
def recover(self, sigR, sigS, msg, msglen, recid, check):
|
||||||
|
"""
|
||||||
|
Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields
|
||||||
|
recid selects which key is recovered
|
||||||
|
if check is non-zero, additional checks are performed
|
||||||
|
"""
|
||||||
|
i = int(recid / 2)
|
||||||
|
|
||||||
|
r = None
|
||||||
|
s = None
|
||||||
|
ctx = None
|
||||||
|
R = None
|
||||||
|
O = None
|
||||||
|
Q = None
|
||||||
|
|
||||||
|
assert len(sigR) == 32, len(sigR)
|
||||||
|
assert len(sigS) == 32, len(sigS)
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = _ssl.BN_bin2bn(bytes(sigR), len(sigR), _ssl.BN_new())
|
||||||
|
s = _ssl.BN_bin2bn(bytes(sigS), len(sigS), _ssl.BN_new())
|
||||||
|
|
||||||
|
group = _ssl.EC_KEY_get0_group(self.k)
|
||||||
|
ctx = _ssl.BN_CTX_new()
|
||||||
|
order = _ssl.BN_CTX_get(ctx)
|
||||||
|
ctx = _ssl.BN_CTX_new()
|
||||||
|
|
||||||
|
if not _ssl.EC_GROUP_get_order(group, order, ctx):
|
||||||
|
return -2
|
||||||
|
|
||||||
|
x = _ssl.BN_CTX_get(ctx)
|
||||||
|
if not _ssl.BN_copy(x, order):
|
||||||
|
return -1
|
||||||
|
if not _ssl.BN_mul_word(x, i):
|
||||||
|
return -1
|
||||||
|
if not _ssl.BN_add(x, x, r):
|
||||||
|
return -1
|
||||||
|
|
||||||
|
field = _ssl.BN_CTX_get(ctx)
|
||||||
|
if not _ssl.EC_GROUP_get_curve_GFp(group, field, None, None, ctx):
|
||||||
|
return -2
|
||||||
|
|
||||||
|
if _ssl.BN_cmp(x, field) >= 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
R = _ssl.EC_POINT_new(group)
|
||||||
|
if R is None:
|
||||||
|
return -2
|
||||||
|
if not _ssl.EC_POINT_set_compressed_coordinates_GFp(group, R, x, recid % 2, ctx):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if check:
|
||||||
|
O = _ssl.EC_POINT_new(group)
|
||||||
|
if O is None:
|
||||||
|
return -2
|
||||||
|
if not _ssl.EC_POINT_mul(group, O, None, R, order, ctx):
|
||||||
|
return -2
|
||||||
|
if not _ssl.EC_POINT_is_at_infinity(group, O):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
Q = _ssl.EC_POINT_new(group)
|
||||||
|
if Q is None:
|
||||||
|
return -2
|
||||||
|
|
||||||
|
n = _ssl.EC_GROUP_get_degree(group)
|
||||||
|
e = _ssl.BN_CTX_get(ctx)
|
||||||
|
if not _ssl.BN_bin2bn(msg, msglen, e):
|
||||||
|
return -1
|
||||||
|
|
||||||
|
if 8 * msglen > n:
|
||||||
|
_ssl.BN_rshift(e, e, 8 - (n & 7))
|
||||||
|
|
||||||
|
zero = _ssl.BN_CTX_get(ctx)
|
||||||
|
# if not _ssl.BN_zero(zero):
|
||||||
|
# return -1
|
||||||
|
if not _ssl.BN_mod_sub(e, zero, e, order, ctx):
|
||||||
|
return -1
|
||||||
|
rr = _ssl.BN_CTX_get(ctx)
|
||||||
|
if not _ssl.BN_mod_inverse(rr, r, order, ctx):
|
||||||
|
return -1
|
||||||
|
sor = _ssl.BN_CTX_get(ctx)
|
||||||
|
if not _ssl.BN_mod_mul(sor, s, rr, order, ctx):
|
||||||
|
return -1
|
||||||
|
eor = _ssl.BN_CTX_get(ctx)
|
||||||
|
if not _ssl.BN_mod_mul(eor, e, rr, order, ctx):
|
||||||
|
return -1
|
||||||
|
if not _ssl.EC_POINT_mul(group, Q, eor, R, sor, ctx):
|
||||||
|
return -2
|
||||||
|
|
||||||
|
if not _ssl.EC_KEY_set_public_key(self.k, Q):
|
||||||
|
return -2
|
||||||
|
|
||||||
|
return 1
|
||||||
|
finally:
|
||||||
|
if r: _ssl.BN_free(r)
|
||||||
|
if s: _ssl.BN_free(s)
|
||||||
|
if ctx: _ssl.BN_CTX_free(ctx)
|
||||||
|
if R: _ssl.EC_POINT_free(R)
|
||||||
|
if O: _ssl.EC_POINT_free(O)
|
||||||
|
if Q: _ssl.EC_POINT_free(Q)
|
||||||
|
|
||||||
|
|
||||||
|
def recover_compact(hash, sig):
|
||||||
|
"""Recover a public key from a compact signature."""
|
||||||
|
if len(sig) != 65:
|
||||||
|
raise ValueError("Signature should be 65 characters, not [%d]" % (len(sig), ))
|
||||||
|
|
||||||
|
recid = (_bord(sig[0]) - 27) & 3
|
||||||
|
compressed = (_bord(sig[0]) - 27) & 4 != 0
|
||||||
|
|
||||||
|
cec_key = CECKey()
|
||||||
|
cec_key.set_compressed(compressed)
|
||||||
|
|
||||||
|
sigR = sig[1:33]
|
||||||
|
sigS = sig[33:65]
|
||||||
|
|
||||||
|
result = cec_key.recover(sigR, sigS, hash, len(hash), recid, 0)
|
||||||
|
|
||||||
|
if result < 1:
|
||||||
|
return False
|
||||||
|
|
||||||
|
pubkey = cec_key.get_pubkey()
|
||||||
|
|
||||||
|
return pubkey
|
||||||
|
|
||||||
|
def encode(val, base, minlen=0):
|
||||||
|
base, minlen = int(base), int(minlen)
|
||||||
|
code_string = ''.join([chr(x) for x in range(256)])
|
||||||
|
result = ""
|
||||||
|
while val > 0:
|
||||||
|
result = code_string[val % base] + result
|
||||||
|
val //= base
|
||||||
|
return code_string[0] * max(minlen - len(result), 0) + result
|
||||||
|
|
||||||
|
def num_to_var_int(x):
|
||||||
|
x = int(x)
|
||||||
|
if x < 253: return chr(x)
|
||||||
|
elif x < 65536: return chr(253)+encode(x, 256, 2)[::-1]
|
||||||
|
elif x < 4294967296: return chr(254) + encode(x, 256, 4)[::-1]
|
||||||
|
else: return chr(255) + encode(x, 256, 8)[::-1]
|
||||||
|
|
||||||
|
|
||||||
|
def msg_magic(message):
|
||||||
|
return "\x18Bitcoin Signed Message:\n" + num_to_var_int( len(message) ) + message
|
||||||
|
|
||||||
|
|
||||||
|
def getMessagePubkey(message, sig):
|
||||||
|
message = msg_magic(message)
|
||||||
|
hash = hashlib.sha256(hashlib.sha256(message).digest()).digest()
|
||||||
|
sig = base64.b64decode(sig)
|
||||||
|
|
||||||
|
pubkey = recover_compact(hash, sig)
|
||||||
|
return pubkey
|
||||||
|
|
||||||
|
def test():
|
||||||
|
sign = "HGbib2kv9gm9IJjDt1FXbXFczZi35u0rZR3iPUIt5GglDDCeIQ7v8eYXVNIaLoJRI4URGZrhwmsYQ9aVtRTnTfQ="
|
||||||
|
pubkey = "044827c756561b8ef6b28b5e53a000805adbf4938ab82e1c2b7f7ea16a0d6face9a509a0a13e794d742210b00581f3e249ebcc705240af2540ea19591091ac1d41"
|
||||||
|
assert getMessagePubkey("hello", sign).encode("hex") == pubkey
|
||||||
|
|
||||||
|
test() # Make sure it working right
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import time, sys
|
||||||
|
sys.path.append("..")
|
||||||
|
from pybitcointools import bitcoin as btctools
|
||||||
|
priv = "5JsunC55XGVqFQj5kPGK4MWgTL26jKbnPhjnmchSNPo75XXCwtk"
|
||||||
|
address = "1N2XWu5soeppX2qUjvrf81rpdbShKJrjTr"
|
||||||
|
sign = btctools.ecdsa_sign("hello", priv) # HGbib2kv9gm9IJjDt1FXbXFczZi35u0rZR3iPUIt5GglDDCeIQ7v8eYXVNIaLoJRI4URGZrhwmsYQ9aVtRTnTfQ=
|
||||||
|
|
||||||
|
s = time.time()
|
||||||
|
for i in range(100):
|
||||||
|
pubkey = getMessagePubkey("hello", sign)
|
||||||
|
verified = btctools.pubkey_to_address(pubkey) == address
|
||||||
|
print "100x Verified", verified, time.time()-s
|
192
src/lib/opensslVerify/opensslVerify-alter2.py
Normal file
192
src/lib/opensslVerify/opensslVerify-alter2.py
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
##
|
||||||
|
## @file contrib/verifymessage/python/terracoin_verifymessage.py
|
||||||
|
## @brief terracoin signed message verification sample script.
|
||||||
|
## @author unknown author ; found on pastebin
|
||||||
|
##
|
||||||
|
|
||||||
|
import ctypes
|
||||||
|
import ctypes.util
|
||||||
|
import hashlib
|
||||||
|
import base64
|
||||||
|
addrtype = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
ssl = ctypes.CDLL("src/lib/opensslVerify/libeay32.dll")
|
||||||
|
except:
|
||||||
|
ssl = ctypes.cdll.LoadLibrary(ctypes.util.find_library('ssl') or ctypes.util.find_library('crypto') or 'libeay32')
|
||||||
|
|
||||||
|
openssl_version = "%.9X" % ssl.SSLeay()
|
||||||
|
|
||||||
|
NID_secp256k1 = 714
|
||||||
|
|
||||||
|
def check_result (val, func, args):
|
||||||
|
if val == 0:
|
||||||
|
raise ValueError
|
||||||
|
else:
|
||||||
|
return ctypes.c_void_p (val)
|
||||||
|
|
||||||
|
ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p
|
||||||
|
ssl.EC_KEY_new_by_curve_name.errcheck = check_result
|
||||||
|
|
||||||
|
POINT_CONVERSION_COMPRESSED = 2
|
||||||
|
POINT_CONVERSION_UNCOMPRESSED = 4
|
||||||
|
|
||||||
|
__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
||||||
|
__b58base = len(__b58chars)
|
||||||
|
|
||||||
|
def b58encode(v):
|
||||||
|
""" encode v, which is a string of bytes, to base58.
|
||||||
|
"""
|
||||||
|
|
||||||
|
long_value = 0L
|
||||||
|
for (i, c) in enumerate(v[::-1]):
|
||||||
|
long_value += (256**i) * ord(c)
|
||||||
|
|
||||||
|
result = ''
|
||||||
|
while long_value >= __b58base:
|
||||||
|
div, mod = divmod(long_value, __b58base)
|
||||||
|
result = __b58chars[mod] + result
|
||||||
|
long_value = div
|
||||||
|
result = __b58chars[long_value] + result
|
||||||
|
|
||||||
|
# Bitcoin does a little leading-zero-compression:
|
||||||
|
# leading 0-bytes in the input become leading-1s
|
||||||
|
nPad = 0
|
||||||
|
for c in v:
|
||||||
|
if c == '\0': nPad += 1
|
||||||
|
else: break
|
||||||
|
|
||||||
|
return (__b58chars[0]*nPad) + result
|
||||||
|
|
||||||
|
def hash_160(public_key):
|
||||||
|
md = hashlib.new('ripemd160')
|
||||||
|
md.update(hashlib.sha256(public_key).digest())
|
||||||
|
return md.digest()
|
||||||
|
|
||||||
|
def hash_160_to_bc_address(h160):
|
||||||
|
vh160 = chr(addrtype) + h160
|
||||||
|
h = Hash(vh160)
|
||||||
|
addr = vh160 + h[0:4]
|
||||||
|
return b58encode(addr)
|
||||||
|
|
||||||
|
def public_key_to_bc_address(public_key):
|
||||||
|
h160 = hash_160(public_key)
|
||||||
|
return hash_160_to_bc_address(h160)
|
||||||
|
|
||||||
|
def msg_magic(message):
|
||||||
|
#return "\x18Bitcoin Signed Message:\n" + chr( len(message) ) + message
|
||||||
|
return "\x18Bitcoin Signed Message:\n" + chr( len(message) ) + message
|
||||||
|
|
||||||
|
def get_address(eckey):
|
||||||
|
size = ssl.i2o_ECPublicKey (eckey, 0)
|
||||||
|
mb = ctypes.create_string_buffer (size)
|
||||||
|
ssl.i2o_ECPublicKey (eckey, ctypes.byref (ctypes.pointer (mb)))
|
||||||
|
return public_key_to_bc_address(mb.raw)
|
||||||
|
|
||||||
|
def Hash(data):
|
||||||
|
return hashlib.sha256(hashlib.sha256(data).digest()).digest()
|
||||||
|
|
||||||
|
def bx(bn, size=32):
|
||||||
|
b = ctypes.create_string_buffer(size)
|
||||||
|
ssl.BN_bn2bin(bn, b);
|
||||||
|
return b.raw.encode('hex')
|
||||||
|
|
||||||
|
def verify_message(address, signature, message):
|
||||||
|
pkey = ssl.EC_KEY_new_by_curve_name (NID_secp256k1)
|
||||||
|
eckey = SetCompactSignature(pkey, Hash(msg_magic(message)), signature)
|
||||||
|
addr = get_address(eckey)
|
||||||
|
return (address == addr)
|
||||||
|
|
||||||
|
def SetCompactSignature(pkey, hash, signature):
|
||||||
|
sig = base64.b64decode(signature)
|
||||||
|
if len(sig) != 65:
|
||||||
|
raise BaseException("Wrong encoding")
|
||||||
|
nV = ord(sig[0])
|
||||||
|
if nV < 27 or nV >= 35:
|
||||||
|
return False
|
||||||
|
if nV >= 31:
|
||||||
|
ssl.EC_KEY_set_conv_form(pkey, POINT_CONVERSION_COMPRESSED)
|
||||||
|
nV -= 4
|
||||||
|
r = ssl.BN_bin2bn (sig[1:33], 32, ssl.BN_new())
|
||||||
|
s = ssl.BN_bin2bn (sig[33:], 32, ssl.BN_new())
|
||||||
|
eckey = ECDSA_SIG_recover_key_GFp(pkey, r, s, hash, len(hash), nV - 27, False);
|
||||||
|
return eckey
|
||||||
|
|
||||||
|
def ECDSA_SIG_recover_key_GFp(eckey, r, s, msg, msglen, recid, check):
|
||||||
|
n = 0
|
||||||
|
i = recid / 2
|
||||||
|
|
||||||
|
group = ssl.EC_KEY_get0_group(eckey)
|
||||||
|
ctx = ssl.BN_CTX_new()
|
||||||
|
ssl.BN_CTX_start(ctx)
|
||||||
|
order = ssl.BN_CTX_get(ctx)
|
||||||
|
ssl.EC_GROUP_get_order(group, order, ctx)
|
||||||
|
x = ssl.BN_CTX_get(ctx)
|
||||||
|
ssl.BN_copy(x, order);
|
||||||
|
ssl.BN_mul_word(x, i);
|
||||||
|
ssl.BN_add(x, x, r)
|
||||||
|
field = ssl.BN_CTX_get(ctx)
|
||||||
|
ssl.EC_GROUP_get_curve_GFp(group, field, None, None, ctx)
|
||||||
|
|
||||||
|
if (ssl.BN_cmp(x, field) >= 0):
|
||||||
|
return False
|
||||||
|
|
||||||
|
R = ssl.EC_POINT_new(group)
|
||||||
|
ssl.EC_POINT_set_compressed_coordinates_GFp(group, R, x, recid % 2, ctx)
|
||||||
|
|
||||||
|
if check:
|
||||||
|
O = ssl.EC_POINT_new(group)
|
||||||
|
ssl.EC_POINT_mul(group, O, None, R, order, ctx)
|
||||||
|
if ssl.EC_POINT_is_at_infinity(group, O):
|
||||||
|
return False
|
||||||
|
|
||||||
|
Q = ssl.EC_POINT_new(group)
|
||||||
|
n = ssl.EC_GROUP_get_degree(group)
|
||||||
|
e = ssl.BN_CTX_get(ctx)
|
||||||
|
ssl.BN_bin2bn(msg, msglen, e)
|
||||||
|
if 8 * msglen > n: ssl.BN_rshift(e, e, 8 - (n & 7))
|
||||||
|
|
||||||
|
|
||||||
|
zero = ssl.BN_CTX_get(ctx)
|
||||||
|
ssl.BN_set_word(zero, 0)
|
||||||
|
ssl.BN_mod_sub(e, zero, e, order, ctx)
|
||||||
|
rr = ssl.BN_CTX_get(ctx);
|
||||||
|
ssl.BN_mod_inverse(rr, r, order, ctx)
|
||||||
|
sor = ssl.BN_CTX_get(ctx)
|
||||||
|
ssl.BN_mod_mul(sor, s, rr, order, ctx)
|
||||||
|
eor = ssl.BN_CTX_get(ctx)
|
||||||
|
ssl.BN_mod_mul(eor, e, rr, order, ctx)
|
||||||
|
ssl.EC_POINT_mul(group, Q, eor, R, sor, ctx)
|
||||||
|
ssl.EC_KEY_set_public_key(eckey, Q)
|
||||||
|
return eckey
|
||||||
|
|
||||||
|
|
||||||
|
def getMessagePubkey(message, sig):
|
||||||
|
pkey = ssl.EC_KEY_new_by_curve_name(NID_secp256k1)
|
||||||
|
eckey = SetCompactSignature(pkey, Hash(msg_magic(message)), sig)
|
||||||
|
size = ssl.i2o_ECPublicKey (eckey, 0)
|
||||||
|
mb = ctypes.create_string_buffer (size)
|
||||||
|
ssl.i2o_ECPublicKey (eckey, ctypes.byref (ctypes.pointer (mb)))
|
||||||
|
return mb.raw
|
||||||
|
|
||||||
|
def test():
|
||||||
|
sign = "HGbib2kv9gm9IJjDt1FXbXFczZi35u0rZR3iPUIt5GglDDCeIQ7v8eYXVNIaLoJRI4URGZrhwmsYQ9aVtRTnTfQ="
|
||||||
|
pubkey = "044827c756561b8ef6b28b5e53a000805adbf4938ab82e1c2b7f7ea16a0d6face9a509a0a13e794d742210b00581f3e249ebcc705240af2540ea19591091ac1d41"
|
||||||
|
assert getMessagePubkey("hello", sign).encode("hex") == pubkey
|
||||||
|
|
||||||
|
test() # Make sure it working right
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import time, os, sys
|
||||||
|
sys.path.append("..")
|
||||||
|
from pybitcointools import bitcoin as btctools
|
||||||
|
priv = "5JsunC55XGVqFQj5kPGK4MWgTL26jKbnPhjnmchSNPo75XXCwtk"
|
||||||
|
address = "1N2XWu5soeppX2qUjvrf81rpdbShKJrjTr"
|
||||||
|
sign = btctools.ecdsa_sign("hello", priv) # HGbib2kv9gm9IJjDt1FXbXFczZi35u0rZR3iPUIt5GglDDCeIQ7v8eYXVNIaLoJRI4URGZrhwmsYQ9aVtRTnTfQ=
|
||||||
|
|
||||||
|
s = time.time()
|
||||||
|
for i in range(100):
|
||||||
|
pubkey = getMessagePubkey("hello", sign)
|
||||||
|
verified = btctools.pubkey_to_address(pubkey) == address
|
||||||
|
print "100x Verified", verified, time.time()-s
|
|
@ -1,346 +1,242 @@
|
||||||
# Code is borrowed from https://github.com/blocktrail/python-bitcoinlib
|
# via http://pastebin.com/H1XikJFd
|
||||||
# Thanks!
|
# -*- Mode: Python -*-
|
||||||
|
|
||||||
import base64, hashlib
|
# This is a combination of http://pastebin.com/bQtdDzHx and
|
||||||
|
# https://github.com/Bitmessage/PyBitmessage/blob/master/src/pyelliptic/openssl.py
|
||||||
|
# that doesn't crash on OSX.
|
||||||
|
# Long message bug fixed by ZeroNet
|
||||||
|
|
||||||
import ctypes
|
import ctypes
|
||||||
import ctypes.util
|
import ctypes.util
|
||||||
_bchr = chr
|
import hashlib
|
||||||
_bord = ord
|
import base64
|
||||||
|
addrtype = 0
|
||||||
|
|
||||||
|
class _OpenSSL:
|
||||||
|
"""
|
||||||
|
Wrapper for OpenSSL using ctypes
|
||||||
|
"""
|
||||||
|
def __init__(self, library):
|
||||||
|
"""
|
||||||
|
Build the wrapper
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self._lib = ctypes.CDLL(library)
|
||||||
|
except:
|
||||||
|
self._lib = ctypes.cdll.LoadLibrary(library)
|
||||||
|
|
||||||
|
self.pointer = ctypes.pointer
|
||||||
|
self.c_int = ctypes.c_int
|
||||||
|
self.byref = ctypes.byref
|
||||||
|
self.create_string_buffer = ctypes.create_string_buffer
|
||||||
|
|
||||||
|
self.BN_new = self._lib.BN_new
|
||||||
|
self.BN_new.restype = ctypes.c_void_p
|
||||||
|
self.BN_new.argtypes = []
|
||||||
|
|
||||||
|
self.BN_copy = self._lib.BN_copy
|
||||||
|
self.BN_copy.restype = ctypes.c_void_p
|
||||||
|
self.BN_copy.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.BN_mul_word = self._lib.BN_mul_word
|
||||||
|
self.BN_mul_word.restype = ctypes.c_int
|
||||||
|
self.BN_mul_word.argtypes = [ctypes.c_void_p, ctypes.c_int]
|
||||||
|
|
||||||
|
self.BN_set_word = self._lib.BN_set_word
|
||||||
|
self.BN_set_word.restype = ctypes.c_int
|
||||||
|
self.BN_set_word.argtypes = [ctypes.c_void_p, ctypes.c_int]
|
||||||
|
|
||||||
|
self.BN_add = self._lib.BN_add
|
||||||
|
self.BN_add.restype = ctypes.c_void_p
|
||||||
|
self.BN_add.argtypes = [ctypes.c_void_p, ctypes.c_void_p,
|
||||||
|
ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.BN_mod_sub = self._lib.BN_mod_sub
|
||||||
|
self.BN_mod_sub.restype = ctypes.c_int
|
||||||
|
self.BN_mod_sub.argtypes = [ctypes.c_void_p, ctypes.c_void_p,
|
||||||
|
ctypes.c_void_p,
|
||||||
|
ctypes.c_void_p,
|
||||||
|
ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.BN_mod_mul = self._lib.BN_mod_mul
|
||||||
|
self.BN_mod_mul.restype = ctypes.c_int
|
||||||
|
self.BN_mod_mul.argtypes = [ctypes.c_void_p, ctypes.c_void_p,
|
||||||
|
ctypes.c_void_p,
|
||||||
|
ctypes.c_void_p,
|
||||||
|
ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.BN_mod_inverse = self._lib.BN_mod_inverse
|
||||||
|
self.BN_mod_inverse.restype = ctypes.c_void_p
|
||||||
|
self.BN_mod_inverse.argtypes = [ctypes.c_void_p, ctypes.c_void_p,
|
||||||
|
ctypes.c_void_p,
|
||||||
|
ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.BN_cmp = self._lib.BN_cmp
|
||||||
|
self.BN_cmp.restype = ctypes.c_int
|
||||||
|
self.BN_cmp.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.BN_bn2bin = self._lib.BN_bn2bin
|
||||||
|
self.BN_bn2bin.restype = ctypes.c_int
|
||||||
|
self.BN_bn2bin.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.BN_bin2bn = self._lib.BN_bin2bn
|
||||||
|
self.BN_bin2bn.restype = ctypes.c_void_p
|
||||||
|
self.BN_bin2bn.argtypes = [ctypes.c_void_p, ctypes.c_int,
|
||||||
|
ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.EC_KEY_new_by_curve_name = self._lib.EC_KEY_new_by_curve_name
|
||||||
|
self.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p
|
||||||
|
self.EC_KEY_new_by_curve_name.argtypes = [ctypes.c_int]
|
||||||
|
|
||||||
|
self.EC_KEY_get0_group = self._lib.EC_KEY_get0_group
|
||||||
|
self.EC_KEY_get0_group.restype = ctypes.c_void_p
|
||||||
|
self.EC_KEY_get0_group.argtypes = [ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.EC_KEY_set_private_key = self._lib.EC_KEY_set_private_key
|
||||||
|
self.EC_KEY_set_private_key.restype = ctypes.c_int
|
||||||
|
self.EC_KEY_set_private_key.argtypes = [ctypes.c_void_p,
|
||||||
|
ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.EC_KEY_set_public_key = self._lib.EC_KEY_set_public_key
|
||||||
|
self.EC_KEY_set_public_key.restype = ctypes.c_int
|
||||||
|
self.EC_KEY_set_public_key.argtypes = [ctypes.c_void_p,
|
||||||
|
ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.EC_POINT_set_compressed_coordinates_GFp = self._lib.EC_POINT_set_compressed_coordinates_GFp
|
||||||
|
self.EC_POINT_set_compressed_coordinates_GFp.restype = ctypes.c_int
|
||||||
|
self.EC_POINT_set_compressed_coordinates_GFp.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.EC_POINT_new = self._lib.EC_POINT_new
|
||||||
|
self.EC_POINT_new.restype = ctypes.c_void_p
|
||||||
|
self.EC_POINT_new.argtypes = [ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.EC_POINT_free = self._lib.EC_POINT_free
|
||||||
|
self.EC_POINT_free.restype = None
|
||||||
|
self.EC_POINT_free.argtypes = [ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.EC_GROUP_get_order = self._lib.EC_GROUP_get_order
|
||||||
|
self.EC_GROUP_get_order.restype = ctypes.c_void_p
|
||||||
|
self.EC_GROUP_get_order.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.EC_GROUP_get_degree = self._lib.EC_GROUP_get_degree
|
||||||
|
self.EC_GROUP_get_degree.restype = ctypes.c_void_p
|
||||||
|
self.EC_GROUP_get_degree.argtypes = [ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.EC_GROUP_get_curve_GFp = self._lib.EC_GROUP_get_curve_GFp
|
||||||
|
self.EC_GROUP_get_curve_GFp.restype = ctypes.c_void_p
|
||||||
|
self.EC_GROUP_get_curve_GFp.argtypes = [ctypes.c_void_p,
|
||||||
|
ctypes.c_void_p,
|
||||||
|
ctypes.c_void_p,
|
||||||
|
ctypes.c_void_p,
|
||||||
|
ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.EC_POINT_mul = self._lib.EC_POINT_mul
|
||||||
|
self.EC_POINT_mul.restype = ctypes.c_int
|
||||||
|
self.EC_POINT_mul.argtypes = [ctypes.c_void_p, ctypes.c_void_p,
|
||||||
|
ctypes.c_void_p, ctypes.c_void_p,
|
||||||
|
ctypes.c_void_p, ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.EC_KEY_set_private_key = self._lib.EC_KEY_set_private_key
|
||||||
|
self.EC_KEY_set_private_key.restype = ctypes.c_int
|
||||||
|
self.EC_KEY_set_private_key.argtypes = [ctypes.c_void_p,
|
||||||
|
ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.EC_KEY_set_conv_form = self._lib.EC_KEY_set_conv_form
|
||||||
|
self.EC_KEY_set_conv_form.restype = None
|
||||||
|
self.EC_KEY_set_conv_form.argtypes = [ctypes.c_void_p,
|
||||||
|
ctypes.c_int]
|
||||||
|
|
||||||
|
self.BN_CTX_new = self._lib.BN_CTX_new
|
||||||
|
self._lib.BN_CTX_new.restype = ctypes.c_void_p
|
||||||
|
self._lib.BN_CTX_new.argtypes = []
|
||||||
|
|
||||||
|
self.BN_CTX_start = self._lib.BN_CTX_start
|
||||||
|
self._lib.BN_CTX_start.restype = ctypes.c_void_p
|
||||||
|
self._lib.BN_CTX_start.argtypes = [ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.BN_CTX_get = self._lib.BN_CTX_get
|
||||||
|
self._lib.BN_CTX_get.restype = ctypes.c_void_p
|
||||||
|
self._lib.BN_CTX_get.argtypes = [ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.ECDSA_sign = self._lib.ECDSA_sign
|
||||||
|
self.ECDSA_sign.restype = ctypes.c_int
|
||||||
|
self.ECDSA_sign.argtypes = [ctypes.c_int, ctypes.c_void_p,
|
||||||
|
ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.ECDSA_verify = self._lib.ECDSA_verify
|
||||||
|
self.ECDSA_verify.restype = ctypes.c_int
|
||||||
|
self.ECDSA_verify.argtypes = [ctypes.c_int, ctypes.c_void_p,
|
||||||
|
ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p]
|
||||||
|
|
||||||
|
self.i2o_ECPublicKey = self._lib.i2o_ECPublicKey
|
||||||
|
self.i2o_ECPublicKey.restype = ctypes.c_void_p
|
||||||
|
self.i2o_ECPublicKey.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_ssl = ctypes.CDLL("src/lib/opensslVerify/libeay32.dll")
|
ssl = _OpenSSL("src/lib/opensslVerify/libeay32.dll")
|
||||||
except:
|
except:
|
||||||
_ssl = ctypes.cdll.LoadLibrary(ctypes.util.find_library('ssl') or ctypes.util.find_library('crypto') or 'libeay32')
|
ssl = _OpenSSL(ctypes.util.find_library('ssl') or ctypes.util.find_library('crypto') or 'libeay32')
|
||||||
|
|
||||||
import sys
|
openssl_version = "%.9X" % ssl._lib.SSLeay()
|
||||||
|
|
||||||
openssl_version = "%.9X" % _ssl.SSLeay()
|
NID_secp256k1 = 714
|
||||||
|
|
||||||
|
def check_result (val, func, args):
|
||||||
# this specifies the curve used with ECDSA.
|
|
||||||
_NID_secp256k1 = 714 # from openssl/obj_mac.h
|
|
||||||
|
|
||||||
# Thx to Sam Devlin for the ctypes magic 64-bit fix.
|
|
||||||
def _check_result (val, func, args):
|
|
||||||
if val == 0:
|
if val == 0:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
else:
|
else:
|
||||||
return ctypes.c_void_p(val)
|
return ctypes.c_void_p (val)
|
||||||
|
|
||||||
_ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p
|
ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p
|
||||||
_ssl.EC_KEY_new_by_curve_name.errcheck = _check_result
|
ssl.EC_KEY_new_by_curve_name.errcheck = check_result
|
||||||
|
|
||||||
# From openssl/ecdsa.h
|
POINT_CONVERSION_COMPRESSED = 2
|
||||||
class ECDSA_SIG_st(ctypes.Structure):
|
POINT_CONVERSION_UNCOMPRESSED = 4
|
||||||
_fields_ = [("r", ctypes.c_void_p),
|
|
||||||
("s", ctypes.c_void_p)]
|
|
||||||
|
|
||||||
class CECKey:
|
__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
||||||
"""Wrapper around OpenSSL's EC_KEY"""
|
__b58base = len(__b58chars)
|
||||||
|
|
||||||
POINT_CONVERSION_COMPRESSED = 2
|
def b58encode(v):
|
||||||
POINT_CONVERSION_UNCOMPRESSED = 4
|
""" encode v, which is a string of bytes, to base58.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
long_value = 0L
|
||||||
self.k = _ssl.EC_KEY_new_by_curve_name(_NID_secp256k1)
|
for (i, c) in enumerate(v[::-1]):
|
||||||
|
long_value += (256**i) * ord(c)
|
||||||
|
|
||||||
def __del__(self):
|
result = ''
|
||||||
if _ssl:
|
while long_value >= __b58base:
|
||||||
_ssl.EC_KEY_free(self.k)
|
div, mod = divmod(long_value, __b58base)
|
||||||
self.k = None
|
result = __b58chars[mod] + result
|
||||||
|
long_value = div
|
||||||
|
result = __b58chars[long_value] + result
|
||||||
|
|
||||||
def set_secretbytes(self, secret):
|
# Bitcoin does a little leading-zero-compression:
|
||||||
priv_key = _ssl.BN_bin2bn(secret, 32, _ssl.BN_new())
|
# leading 0-bytes in the input become leading-1s
|
||||||
group = _ssl.EC_KEY_get0_group(self.k)
|
nPad = 0
|
||||||
pub_key = _ssl.EC_POINT_new(group)
|
for c in v:
|
||||||
ctx = _ssl.BN_CTX_new()
|
if c == '\0': nPad += 1
|
||||||
if not _ssl.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx):
|
else: break
|
||||||
raise ValueError("Could not derive public key from the supplied secret.")
|
|
||||||
_ssl.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx)
|
|
||||||
_ssl.EC_KEY_set_private_key(self.k, priv_key)
|
|
||||||
_ssl.EC_KEY_set_public_key(self.k, pub_key)
|
|
||||||
_ssl.EC_POINT_free(pub_key)
|
|
||||||
_ssl.BN_CTX_free(ctx)
|
|
||||||
return self.k
|
|
||||||
|
|
||||||
def set_privkey(self, key):
|
return (__b58chars[0]*nPad) + result
|
||||||
self.mb = ctypes.create_string_buffer(key)
|
|
||||||
return _ssl.d2i_ECPrivateKey(ctypes.byref(self.k), ctypes.byref(ctypes.pointer(self.mb)), len(key))
|
|
||||||
|
|
||||||
def set_pubkey(self, key):
|
def hash_160(public_key):
|
||||||
self.mb = ctypes.create_string_buffer(key)
|
md = hashlib.new('ripemd160')
|
||||||
return _ssl.o2i_ECPublicKey(ctypes.byref(self.k), ctypes.byref(ctypes.pointer(self.mb)), len(key))
|
md.update(hashlib.sha256(public_key).digest())
|
||||||
|
return md.digest()
|
||||||
|
|
||||||
def get_privkey(self):
|
def hash_160_to_bc_address(h160):
|
||||||
size = _ssl.i2d_ECPrivateKey(self.k, 0)
|
vh160 = chr(addrtype) + h160
|
||||||
mb_pri = ctypes.create_string_buffer(size)
|
h = Hash(vh160)
|
||||||
_ssl.i2d_ECPrivateKey(self.k, ctypes.byref(ctypes.pointer(mb_pri)))
|
addr = vh160 + h[0:4]
|
||||||
return mb_pri.raw
|
return b58encode(addr)
|
||||||
|
|
||||||
def get_pubkey(self):
|
def public_key_to_bc_address(public_key):
|
||||||
size = _ssl.i2o_ECPublicKey(self.k, 0)
|
h160 = hash_160(public_key)
|
||||||
mb = ctypes.create_string_buffer(size)
|
return hash_160_to_bc_address(h160)
|
||||||
_ssl.i2o_ECPublicKey(self.k, ctypes.byref(ctypes.pointer(mb)))
|
|
||||||
return mb.raw
|
|
||||||
|
|
||||||
def get_raw_ecdh_key(self, other_pubkey):
|
|
||||||
ecdh_keybuffer = ctypes.create_string_buffer(32)
|
|
||||||
r = _ssl.ECDH_compute_key(ctypes.pointer(ecdh_keybuffer), 32,
|
|
||||||
_ssl.EC_KEY_get0_public_key(other_pubkey.k),
|
|
||||||
self.k, 0)
|
|
||||||
if r != 32:
|
|
||||||
raise Exception('CKey.get_ecdh_key(): ECDH_compute_key() failed')
|
|
||||||
return ecdh_keybuffer.raw
|
|
||||||
|
|
||||||
def get_ecdh_key(self, other_pubkey, kdf=lambda k: hashlib.sha256(k).digest()):
|
|
||||||
# FIXME: be warned it's not clear what the kdf should be as a default
|
|
||||||
r = self.get_raw_ecdh_key(other_pubkey)
|
|
||||||
return kdf(r)
|
|
||||||
|
|
||||||
def sign(self, hash):
|
|
||||||
if not isinstance(hash, bytes):
|
|
||||||
raise TypeError('Hash must be bytes instance; got %r' % hash.__class__)
|
|
||||||
if len(hash) != 32:
|
|
||||||
raise ValueError('Hash must be exactly 32 bytes long')
|
|
||||||
|
|
||||||
sig_size0 = ctypes.c_uint32()
|
|
||||||
sig_size0.value = _ssl.ECDSA_size(self.k)
|
|
||||||
mb_sig = ctypes.create_string_buffer(sig_size0.value)
|
|
||||||
result = _ssl.ECDSA_sign(0, hash, len(hash), mb_sig, ctypes.byref(sig_size0), self.k)
|
|
||||||
assert 1 == result
|
|
||||||
if bitcoin.core.script.IsLowDERSignature(mb_sig.raw[:sig_size0.value]):
|
|
||||||
return mb_sig.raw[:sig_size0.value]
|
|
||||||
else:
|
|
||||||
return self.signature_to_low_s(mb_sig.raw[:sig_size0.value])
|
|
||||||
|
|
||||||
def sign_compact(self, hash):
|
|
||||||
if not isinstance(hash, bytes):
|
|
||||||
raise TypeError('Hash must be bytes instance; got %r' % hash.__class__)
|
|
||||||
if len(hash) != 32:
|
|
||||||
raise ValueError('Hash must be exactly 32 bytes long')
|
|
||||||
|
|
||||||
sig_size0 = ctypes.c_uint32()
|
|
||||||
sig_size0.value = _ssl.ECDSA_size(self.k)
|
|
||||||
mb_sig = ctypes.create_string_buffer(sig_size0.value)
|
|
||||||
result = _ssl.ECDSA_sign(0, hash, len(hash), mb_sig, ctypes.byref(sig_size0), self.k)
|
|
||||||
assert 1 == result
|
|
||||||
|
|
||||||
if bitcoin.core.script.IsLowDERSignature(mb_sig.raw[:sig_size0.value]):
|
|
||||||
sig = mb_sig.raw[:sig_size0.value]
|
|
||||||
else:
|
|
||||||
sig = self.signature_to_low_s(mb_sig.raw[:sig_size0.value])
|
|
||||||
|
|
||||||
sig = bitcoin.core.DERSignature.deserialize(sig)
|
|
||||||
|
|
||||||
r_val = sig.r
|
|
||||||
s_val = sig.s
|
|
||||||
|
|
||||||
# assert that the r and s are less than 32 long, excluding leading 0s
|
|
||||||
assert len(r_val) <= 32 or r_val[0:-32] == b'\x00'
|
|
||||||
assert len(s_val) <= 32 or s_val[0:-32] == b'\x00'
|
|
||||||
|
|
||||||
# ensure r and s are always 32 chars long by 0padding
|
|
||||||
r_val = ((b'\x00' * 32) + r_val)[-32:]
|
|
||||||
s_val = ((b'\x00' * 32) + s_val)[-32:]
|
|
||||||
|
|
||||||
# tmp pubkey of self, but always compressed
|
|
||||||
pubkey = CECKey()
|
|
||||||
pubkey.set_pubkey(self.get_pubkey())
|
|
||||||
pubkey.set_compressed(True)
|
|
||||||
|
|
||||||
# bitcoin core does <4, but I've seen other places do <2 and I've never seen a i > 1 so far
|
|
||||||
for i in range(0, 4):
|
|
||||||
cec_key = CECKey()
|
|
||||||
cec_key.set_compressed(True)
|
|
||||||
|
|
||||||
result = cec_key.recover(r_val, s_val, hash, len(hash), i, 1)
|
|
||||||
if result == 1:
|
|
||||||
if cec_key.get_pubkey() == pubkey.get_pubkey():
|
|
||||||
return r_val + s_val, i
|
|
||||||
|
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
def signature_to_low_s(self, sig):
|
|
||||||
der_sig = ECDSA_SIG_st()
|
|
||||||
_ssl.d2i_ECDSA_SIG(ctypes.byref(ctypes.pointer(der_sig)), ctypes.byref(ctypes.c_char_p(sig)), len(sig))
|
|
||||||
group = _ssl.EC_KEY_get0_group(self.k)
|
|
||||||
order = _ssl.BN_new()
|
|
||||||
halforder = _ssl.BN_new()
|
|
||||||
ctx = _ssl.BN_CTX_new()
|
|
||||||
_ssl.EC_GROUP_get_order(group, order, ctx)
|
|
||||||
_ssl.BN_rshift1(halforder, order)
|
|
||||||
|
|
||||||
# Verify that s is over half the order of the curve before we actually subtract anything from it
|
|
||||||
if _ssl.BN_cmp(der_sig.s, halforder) > 0:
|
|
||||||
_ssl.BN_sub(der_sig.s, order, der_sig.s)
|
|
||||||
|
|
||||||
_ssl.BN_free(halforder)
|
|
||||||
_ssl.BN_free(order)
|
|
||||||
_ssl.BN_CTX_free(ctx)
|
|
||||||
|
|
||||||
derlen = _ssl.i2d_ECDSA_SIG(ctypes.pointer(der_sig), 0)
|
|
||||||
if derlen == 0:
|
|
||||||
_ssl.ECDSA_SIG_free(der_sig)
|
|
||||||
return None
|
|
||||||
new_sig = ctypes.create_string_buffer(derlen)
|
|
||||||
_ssl.i2d_ECDSA_SIG(ctypes.pointer(der_sig), ctypes.byref(ctypes.pointer(new_sig)))
|
|
||||||
_ssl.BN_free(der_sig.r)
|
|
||||||
_ssl.BN_free(der_sig.s)
|
|
||||||
|
|
||||||
return new_sig.raw
|
|
||||||
|
|
||||||
def verify(self, hash, sig):
|
|
||||||
"""Verify a DER signature"""
|
|
||||||
if not sig:
|
|
||||||
return false
|
|
||||||
|
|
||||||
# New versions of OpenSSL will reject non-canonical DER signatures. de/re-serialize first.
|
|
||||||
norm_sig = ctypes.c_void_p(0)
|
|
||||||
_ssl.d2i_ECDSA_SIG(ctypes.byref(norm_sig), ctypes.byref(ctypes.c_char_p(sig)), len(sig))
|
|
||||||
|
|
||||||
derlen = _ssl.i2d_ECDSA_SIG(norm_sig, 0)
|
|
||||||
if derlen == 0:
|
|
||||||
_ssl.ECDSA_SIG_free(norm_sig)
|
|
||||||
return false
|
|
||||||
|
|
||||||
norm_der = ctypes.create_string_buffer(derlen)
|
|
||||||
_ssl.i2d_ECDSA_SIG(norm_sig, ctypes.byref(ctypes.pointer(norm_der)))
|
|
||||||
_ssl.ECDSA_SIG_free(norm_sig)
|
|
||||||
|
|
||||||
# -1 = error, 0 = bad sig, 1 = good
|
|
||||||
return _ssl.ECDSA_verify(0, hash, len(hash), norm_der, derlen, self.k) == 1
|
|
||||||
|
|
||||||
def set_compressed(self, compressed):
|
|
||||||
if compressed:
|
|
||||||
form = self.POINT_CONVERSION_COMPRESSED
|
|
||||||
else:
|
|
||||||
form = self.POINT_CONVERSION_UNCOMPRESSED
|
|
||||||
_ssl.EC_KEY_set_conv_form(self.k, form)
|
|
||||||
|
|
||||||
def recover(self, sigR, sigS, msg, msglen, recid, check):
|
|
||||||
"""
|
|
||||||
Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields
|
|
||||||
recid selects which key is recovered
|
|
||||||
if check is non-zero, additional checks are performed
|
|
||||||
"""
|
|
||||||
i = int(recid / 2)
|
|
||||||
|
|
||||||
r = None
|
|
||||||
s = None
|
|
||||||
ctx = None
|
|
||||||
R = None
|
|
||||||
O = None
|
|
||||||
Q = None
|
|
||||||
|
|
||||||
assert len(sigR) == 32, len(sigR)
|
|
||||||
assert len(sigS) == 32, len(sigS)
|
|
||||||
|
|
||||||
try:
|
|
||||||
r = _ssl.BN_bin2bn(bytes(sigR), len(sigR), _ssl.BN_new())
|
|
||||||
s = _ssl.BN_bin2bn(bytes(sigS), len(sigS), _ssl.BN_new())
|
|
||||||
|
|
||||||
group = _ssl.EC_KEY_get0_group(self.k)
|
|
||||||
ctx = _ssl.BN_CTX_new()
|
|
||||||
order = _ssl.BN_CTX_get(ctx)
|
|
||||||
ctx = _ssl.BN_CTX_new()
|
|
||||||
|
|
||||||
if not _ssl.EC_GROUP_get_order(group, order, ctx):
|
|
||||||
return -2
|
|
||||||
|
|
||||||
x = _ssl.BN_CTX_get(ctx)
|
|
||||||
if not _ssl.BN_copy(x, order):
|
|
||||||
return -1
|
|
||||||
if not _ssl.BN_mul_word(x, i):
|
|
||||||
return -1
|
|
||||||
if not _ssl.BN_add(x, x, r):
|
|
||||||
return -1
|
|
||||||
|
|
||||||
field = _ssl.BN_CTX_get(ctx)
|
|
||||||
if not _ssl.EC_GROUP_get_curve_GFp(group, field, None, None, ctx):
|
|
||||||
return -2
|
|
||||||
|
|
||||||
if _ssl.BN_cmp(x, field) >= 0:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
R = _ssl.EC_POINT_new(group)
|
|
||||||
if R is None:
|
|
||||||
return -2
|
|
||||||
if not _ssl.EC_POINT_set_compressed_coordinates_GFp(group, R, x, recid % 2, ctx):
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if check:
|
|
||||||
O = _ssl.EC_POINT_new(group)
|
|
||||||
if O is None:
|
|
||||||
return -2
|
|
||||||
if not _ssl.EC_POINT_mul(group, O, None, R, order, ctx):
|
|
||||||
return -2
|
|
||||||
if not _ssl.EC_POINT_is_at_infinity(group, O):
|
|
||||||
return 0
|
|
||||||
|
|
||||||
Q = _ssl.EC_POINT_new(group)
|
|
||||||
if Q is None:
|
|
||||||
return -2
|
|
||||||
|
|
||||||
n = _ssl.EC_GROUP_get_degree(group)
|
|
||||||
e = _ssl.BN_CTX_get(ctx)
|
|
||||||
if not _ssl.BN_bin2bn(msg, msglen, e):
|
|
||||||
return -1
|
|
||||||
|
|
||||||
if 8 * msglen > n:
|
|
||||||
_ssl.BN_rshift(e, e, 8 - (n & 7))
|
|
||||||
|
|
||||||
zero = _ssl.BN_CTX_get(ctx)
|
|
||||||
# if not _ssl.BN_zero(zero):
|
|
||||||
# return -1
|
|
||||||
if not _ssl.BN_mod_sub(e, zero, e, order, ctx):
|
|
||||||
return -1
|
|
||||||
rr = _ssl.BN_CTX_get(ctx)
|
|
||||||
if not _ssl.BN_mod_inverse(rr, r, order, ctx):
|
|
||||||
return -1
|
|
||||||
sor = _ssl.BN_CTX_get(ctx)
|
|
||||||
if not _ssl.BN_mod_mul(sor, s, rr, order, ctx):
|
|
||||||
return -1
|
|
||||||
eor = _ssl.BN_CTX_get(ctx)
|
|
||||||
if not _ssl.BN_mod_mul(eor, e, rr, order, ctx):
|
|
||||||
return -1
|
|
||||||
if not _ssl.EC_POINT_mul(group, Q, eor, R, sor, ctx):
|
|
||||||
return -2
|
|
||||||
|
|
||||||
if not _ssl.EC_KEY_set_public_key(self.k, Q):
|
|
||||||
return -2
|
|
||||||
|
|
||||||
return 1
|
|
||||||
finally:
|
|
||||||
if r: _ssl.BN_free(r)
|
|
||||||
if s: _ssl.BN_free(s)
|
|
||||||
if ctx: _ssl.BN_CTX_free(ctx)
|
|
||||||
if R: _ssl.EC_POINT_free(R)
|
|
||||||
if O: _ssl.EC_POINT_free(O)
|
|
||||||
if Q: _ssl.EC_POINT_free(Q)
|
|
||||||
|
|
||||||
|
|
||||||
def recover_compact(hash, sig):
|
|
||||||
"""Recover a public key from a compact signature."""
|
|
||||||
if len(sig) != 65:
|
|
||||||
raise ValueError("Signature should be 65 characters, not [%d]" % (len(sig), ))
|
|
||||||
|
|
||||||
recid = (_bord(sig[0]) - 27) & 3
|
|
||||||
compressed = (_bord(sig[0]) - 27) & 4 != 0
|
|
||||||
|
|
||||||
cec_key = CECKey()
|
|
||||||
cec_key.set_compressed(compressed)
|
|
||||||
|
|
||||||
sigR = sig[1:33]
|
|
||||||
sigS = sig[33:65]
|
|
||||||
|
|
||||||
result = cec_key.recover(sigR, sigS, hash, len(hash), recid, 0)
|
|
||||||
|
|
||||||
if result < 1:
|
|
||||||
return False
|
|
||||||
|
|
||||||
pubkey = cec_key.get_pubkey()
|
|
||||||
|
|
||||||
return pubkey
|
|
||||||
|
|
||||||
def encode(val, base, minlen=0):
|
def encode(val, base, minlen=0):
|
||||||
base, minlen = int(base), int(minlen)
|
base, minlen = int(base), int(minlen)
|
||||||
|
@ -358,18 +254,107 @@ def num_to_var_int(x):
|
||||||
elif x < 4294967296: return chr(254) + encode(x, 256, 4)[::-1]
|
elif x < 4294967296: return chr(254) + encode(x, 256, 4)[::-1]
|
||||||
else: return chr(255) + encode(x, 256, 8)[::-1]
|
else: return chr(255) + encode(x, 256, 8)[::-1]
|
||||||
|
|
||||||
|
|
||||||
def msg_magic(message):
|
def msg_magic(message):
|
||||||
return "\x18Bitcoin Signed Message:\n" + num_to_var_int( len(message) ) + message
|
return "\x18Bitcoin Signed Message:\n" + num_to_var_int( len(message) ) + message
|
||||||
|
|
||||||
|
def get_address(eckey):
|
||||||
|
size = ssl.i2o_ECPublicKey (eckey, 0)
|
||||||
|
mb = ctypes.create_string_buffer (size)
|
||||||
|
ssl.i2o_ECPublicKey (eckey, ctypes.byref (ctypes.pointer (mb)))
|
||||||
|
return public_key_to_bc_address(mb.raw)
|
||||||
|
|
||||||
|
def Hash(data):
|
||||||
|
return hashlib.sha256(hashlib.sha256(data).digest()).digest()
|
||||||
|
|
||||||
|
def bx(bn, size=32):
|
||||||
|
b = ctypes.create_string_buffer(size)
|
||||||
|
ssl.BN_bn2bin(bn, b);
|
||||||
|
return b.raw.encode('hex')
|
||||||
|
|
||||||
|
def verify_message(address, signature, message):
|
||||||
|
pkey = ssl.EC_KEY_new_by_curve_name(NID_secp256k1)
|
||||||
|
eckey = SetCompactSignature(pkey, Hash(msg_magic(message)), signature)
|
||||||
|
addr = get_address(eckey)
|
||||||
|
return (address == addr)
|
||||||
|
|
||||||
|
def SetCompactSignature(pkey, hash, signature):
|
||||||
|
sig = base64.b64decode(signature)
|
||||||
|
if len(sig) != 65:
|
||||||
|
raise BaseException("Wrong encoding")
|
||||||
|
nV = ord(sig[0])
|
||||||
|
if nV < 27 or nV >= 35:
|
||||||
|
return False
|
||||||
|
if nV >= 31:
|
||||||
|
ssl.EC_KEY_set_conv_form(pkey, POINT_CONVERSION_COMPRESSED)
|
||||||
|
nV -= 4
|
||||||
|
r = ssl.BN_bin2bn (sig[1:33], 32, None)
|
||||||
|
s = ssl.BN_bin2bn (sig[33:], 32, None)
|
||||||
|
eckey = ECDSA_SIG_recover_key_GFp(pkey, r, s, hash, len(hash), nV - 27,
|
||||||
|
False);
|
||||||
|
return eckey
|
||||||
|
|
||||||
|
def ECDSA_SIG_recover_key_GFp(eckey, r, s, msg, msglen, recid, check):
|
||||||
|
n = 0
|
||||||
|
i = recid / 2
|
||||||
|
|
||||||
|
group = ssl.EC_KEY_get0_group(eckey)
|
||||||
|
ctx = ssl.BN_CTX_new()
|
||||||
|
ssl.BN_CTX_start(ctx)
|
||||||
|
order = ssl.BN_CTX_get(ctx)
|
||||||
|
ssl.EC_GROUP_get_order(group, order, ctx)
|
||||||
|
x = ssl.BN_CTX_get(ctx)
|
||||||
|
ssl.BN_copy(x, order);
|
||||||
|
ssl.BN_mul_word(x, i);
|
||||||
|
ssl.BN_add(x, x, r)
|
||||||
|
field = ssl.BN_CTX_get(ctx)
|
||||||
|
ssl.EC_GROUP_get_curve_GFp(group, field, None, None, ctx)
|
||||||
|
|
||||||
|
if (ssl.BN_cmp(x, field) >= 0):
|
||||||
|
return False
|
||||||
|
|
||||||
|
R = ssl.EC_POINT_new(group)
|
||||||
|
ssl.EC_POINT_set_compressed_coordinates_GFp(group, R, x, recid % 2, ctx)
|
||||||
|
|
||||||
|
if check:
|
||||||
|
O = ssl.EC_POINT_new(group)
|
||||||
|
ssl.EC_POINT_mul(group, O, None, R, order, ctx)
|
||||||
|
if ssl.EC_POINT_is_at_infinity(group, O):
|
||||||
|
return False
|
||||||
|
|
||||||
|
Q = ssl.EC_POINT_new(group)
|
||||||
|
n = ssl.EC_GROUP_get_degree(group)
|
||||||
|
e = ssl.BN_CTX_get(ctx)
|
||||||
|
ssl.BN_bin2bn(msg, msglen, e)
|
||||||
|
if 8 * msglen > n: ssl.BN_rshift(e, e, 8 - (n & 7))
|
||||||
|
|
||||||
|
zero = ssl.BN_CTX_get(ctx)
|
||||||
|
ssl.BN_set_word(zero, 0)
|
||||||
|
ssl.BN_mod_sub(e, zero, e, order, ctx)
|
||||||
|
rr = ssl.BN_CTX_get(ctx);
|
||||||
|
ssl.BN_mod_inverse(rr, r, order, ctx)
|
||||||
|
sor = ssl.BN_CTX_get(ctx)
|
||||||
|
ssl.BN_mod_mul(sor, s, rr, order, ctx)
|
||||||
|
eor = ssl.BN_CTX_get(ctx)
|
||||||
|
ssl.BN_mod_mul(eor, e, rr, order, ctx)
|
||||||
|
ssl.EC_POINT_mul(group, Q, eor, R, sor, ctx)
|
||||||
|
ssl.EC_KEY_set_public_key(eckey, Q)
|
||||||
|
return eckey
|
||||||
|
|
||||||
|
def close():
|
||||||
|
import _ctypes
|
||||||
|
if "FreeLibrary" in dir(_ctypes):
|
||||||
|
_ctypes.FreeLibrary(ssl._lib._handle)
|
||||||
|
else:
|
||||||
|
_ctypes.dlclose(ssl._lib._handle)
|
||||||
|
|
||||||
|
|
||||||
def getMessagePubkey(message, sig):
|
def getMessagePubkey(message, sig):
|
||||||
message = msg_magic(message)
|
pkey = ssl.EC_KEY_new_by_curve_name(NID_secp256k1)
|
||||||
hash = hashlib.sha256(hashlib.sha256(message).digest()).digest()
|
eckey = SetCompactSignature(pkey, Hash(msg_magic(message)), sig)
|
||||||
sig = base64.b64decode(sig)
|
size = ssl.i2o_ECPublicKey (eckey, 0)
|
||||||
|
mb = ctypes.create_string_buffer (size)
|
||||||
pubkey = recover_compact(hash, sig)
|
ssl.i2o_ECPublicKey (eckey, ctypes.byref (ctypes.pointer (mb)))
|
||||||
return pubkey
|
return mb.raw
|
||||||
|
|
||||||
def test():
|
def test():
|
||||||
sign = "HGbib2kv9gm9IJjDt1FXbXFczZi35u0rZR3iPUIt5GglDDCeIQ7v8eYXVNIaLoJRI4URGZrhwmsYQ9aVtRTnTfQ="
|
sign = "HGbib2kv9gm9IJjDt1FXbXFczZi35u0rZR3iPUIt5GglDDCeIQ7v8eYXVNIaLoJRI4URGZrhwmsYQ9aVtRTnTfQ="
|
||||||
|
@ -379,8 +364,10 @@ def test():
|
||||||
test() # Make sure it working right
|
test() # Make sure it working right
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import time
|
import time, os, sys
|
||||||
|
sys.path.append("..")
|
||||||
from pybitcointools import bitcoin as btctools
|
from pybitcointools import bitcoin as btctools
|
||||||
|
print "OpenSSL version %s" % openssl_version
|
||||||
priv = "5JsunC55XGVqFQj5kPGK4MWgTL26jKbnPhjnmchSNPo75XXCwtk"
|
priv = "5JsunC55XGVqFQj5kPGK4MWgTL26jKbnPhjnmchSNPo75XXCwtk"
|
||||||
address = "1N2XWu5soeppX2qUjvrf81rpdbShKJrjTr"
|
address = "1N2XWu5soeppX2qUjvrf81rpdbShKJrjTr"
|
||||||
sign = btctools.ecdsa_sign("hello", priv) # HGbib2kv9gm9IJjDt1FXbXFczZi35u0rZR3iPUIt5GglDDCeIQ7v8eYXVNIaLoJRI4URGZrhwmsYQ9aVtRTnTfQ=
|
sign = btctools.ecdsa_sign("hello", priv) # HGbib2kv9gm9IJjDt1FXbXFczZi35u0rZR3iPUIt5GglDDCeIQ7v8eYXVNIaLoJRI4URGZrhwmsYQ9aVtRTnTfQ=
|
||||||
|
|
16
src/lib/opensslVerify/stablityTest.py
Normal file
16
src/lib/opensslVerify/stablityTest.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import opensslVerify, gevent, time
|
||||||
|
from gevent import monkey; monkey.patch_all(thread=False, ssl=False)
|
||||||
|
|
||||||
|
def test():
|
||||||
|
data = "A"*1024
|
||||||
|
sign = "G2Jo8dDa+jqvJipft9E3kfrAxjESWLBpVtuGIiEBCD/UUyHmRMYNqnlWeOiaHHpja5LOP+U5CanRALfOjCSYIa8="
|
||||||
|
for i in range(5*1000):
|
||||||
|
if i%1000 == 0:
|
||||||
|
print i, len(data)
|
||||||
|
data += data+"A"
|
||||||
|
time.sleep(0)
|
||||||
|
pub = opensslVerify.getMessagePubkey(data, sign)
|
||||||
|
|
||||||
|
print repr(pub), len(data)
|
||||||
|
|
||||||
|
gevent.joinall([gevent.spawn(test), gevent.spawn(test)])
|
32
src/main.py
32
src/main.py
|
@ -111,7 +111,7 @@ class Actions:
|
||||||
logging.info("Site created!")
|
logging.info("Site created!")
|
||||||
|
|
||||||
|
|
||||||
def siteSign(self, address, privatekey=None, inner_path="content.json"):
|
def siteSign(self, address, privatekey=None, inner_path="content.json", publish=False):
|
||||||
from Site import Site
|
from Site import Site
|
||||||
logging.info("Signing site: %s..." % address)
|
logging.info("Signing site: %s..." % address)
|
||||||
site = Site(address, allow_create = False)
|
site = Site(address, allow_create = False)
|
||||||
|
@ -119,7 +119,9 @@ class Actions:
|
||||||
if not privatekey: # If no privatekey in args then ask it now
|
if not privatekey: # If no privatekey in args then ask it now
|
||||||
import getpass
|
import getpass
|
||||||
privatekey = getpass.getpass("Private key (input hidden):")
|
privatekey = getpass.getpass("Private key (input hidden):")
|
||||||
site.content_manager.sign(inner_path=inner_path, privatekey=privatekey, update_changed_files=True)
|
succ = site.content_manager.sign(inner_path=inner_path, privatekey=privatekey, update_changed_files=True)
|
||||||
|
if succ and publish:
|
||||||
|
self.sitePublish(address, inner_path=inner_path)
|
||||||
|
|
||||||
|
|
||||||
def siteVerify(self, address):
|
def siteVerify(self, address):
|
||||||
|
@ -128,16 +130,18 @@ class Actions:
|
||||||
s = time.time()
|
s = time.time()
|
||||||
logging.info("Verifing site: %s..." % address)
|
logging.info("Verifing site: %s..." % address)
|
||||||
site = Site(address)
|
site = Site(address)
|
||||||
|
bad_files = []
|
||||||
|
|
||||||
for content_inner_path in site.content_manager.contents:
|
for content_inner_path in site.content_manager.contents:
|
||||||
logging.info("Verifing %s signature..." % content_inner_path)
|
logging.info("Verifing %s signature..." % content_inner_path)
|
||||||
if site.content_manager.verifyFile(content_inner_path, site.storage.open(content_inner_path, "rb"), ignore_same=False) == True:
|
if site.content_manager.verifyFile(content_inner_path, site.storage.open(content_inner_path, "rb"), ignore_same=False) == True:
|
||||||
logging.info("[OK] %s signed by address %s!" % (content_inner_path, address))
|
logging.info("[OK] %s signed by address %s!" % (content_inner_path, address))
|
||||||
else:
|
else:
|
||||||
logging.error("[ERROR] %s not signed by address %s!" % (content_inner_path, address))
|
logging.error("[ERROR] %s: invalid file!" % content_inner_path)
|
||||||
|
bad_files += content_inner_path
|
||||||
|
|
||||||
logging.info("Verifying site files...")
|
logging.info("Verifying site files...")
|
||||||
bad_files = site.storage.verifyFiles()
|
bad_files += site.storage.verifyFiles()
|
||||||
if not bad_files:
|
if not bad_files:
|
||||||
logging.info("[OK] All file sha512sum matches! (%.3fs)" % (time.time()-s))
|
logging.info("[OK] All file sha512sum matches! (%.3fs)" % (time.time()-s))
|
||||||
else:
|
else:
|
||||||
|
@ -197,17 +201,20 @@ class Actions:
|
||||||
else: # Just ask the tracker
|
else: # Just ask the tracker
|
||||||
logging.info("Gathering peers from tracker")
|
logging.info("Gathering peers from tracker")
|
||||||
site.announce() # Gather peers
|
site.announce() # Gather peers
|
||||||
site.publish(20, inner_path) # Push to 20 peers
|
published = site.publish(20, inner_path) # Push to 20 peers
|
||||||
time.sleep(3)
|
if published > 0:
|
||||||
logging.info("Serving files (max 60s)...")
|
time.sleep(3)
|
||||||
gevent.joinall([file_server_thread], timeout=60)
|
logging.info("Serving files (max 60s)...")
|
||||||
logging.info("Done.")
|
gevent.joinall([file_server_thread], timeout=60)
|
||||||
|
logging.info("Done.")
|
||||||
|
else:
|
||||||
|
logging.info("No peers found for this site, sitePublish command only works if you already have peers serving your site")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Crypto commands
|
# Crypto commands
|
||||||
|
|
||||||
def cryptoPrivatekeyToAddress(self, privatekey=None):
|
def cryptPrivatekeyToAddress(self, privatekey=None):
|
||||||
from Crypt import CryptBitcoin
|
from Crypt import CryptBitcoin
|
||||||
if not privatekey: # If no privatekey in args then ask it now
|
if not privatekey: # If no privatekey in args then ask it now
|
||||||
import getpass
|
import getpass
|
||||||
|
@ -216,6 +223,11 @@ class Actions:
|
||||||
print CryptBitcoin.privatekeyToAddress(privatekey)
|
print CryptBitcoin.privatekeyToAddress(privatekey)
|
||||||
|
|
||||||
|
|
||||||
|
def cryptSign(self, message, privatekey):
|
||||||
|
from Crypt import CryptBitcoin
|
||||||
|
print CryptBitcoin.sign(message, privatekey)
|
||||||
|
|
||||||
|
|
||||||
# Peer
|
# Peer
|
||||||
|
|
||||||
def peerPing(self, peer_ip, peer_port):
|
def peerPing(self, peer_ip, peer_port):
|
||||||
|
|
|
@ -9,7 +9,7 @@ class Noparallel(object): # Only allow function running once in same time
|
||||||
|
|
||||||
def __call__(self, func):
|
def __call__(self, func):
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
key = (func, tuple(args), tuple(kwargs)) # Unique key for function including parameters
|
key = (func, tuple(args), tuple(kwargs.items())) # Unique key for function including parameters
|
||||||
if key in self.threads: # Thread already running (if using blocking mode)
|
if key in self.threads: # Thread already running (if using blocking mode)
|
||||||
thread = self.threads[key]
|
thread = self.threads[key]
|
||||||
if self.blocking:
|
if self.blocking:
|
||||||
|
@ -24,14 +24,13 @@ class Noparallel(object): # Only allow function running once in same time
|
||||||
return thread
|
return thread
|
||||||
else: # Thread not running
|
else: # Thread not running
|
||||||
thread = gevent.spawn(func, *args, **kwargs) # Spawning new thread
|
thread = gevent.spawn(func, *args, **kwargs) # Spawning new thread
|
||||||
|
thread.link(lambda thread: self.cleanup(key, thread))
|
||||||
self.threads[key] = thread
|
self.threads[key] = thread
|
||||||
if self.blocking: # Wait for finish
|
if self.blocking: # Wait for finish
|
||||||
thread.join()
|
thread.join()
|
||||||
ret = thread.value
|
ret = thread.value
|
||||||
if key in self.threads: del(self.threads[key]) # Allowing it to run again
|
|
||||||
return ret
|
return ret
|
||||||
else: # No blocking just return the thread
|
else: # No blocking just return the thread
|
||||||
thread.link(lambda thread: self.cleanup(key, thread))
|
|
||||||
return thread
|
return thread
|
||||||
wrapper.func_name = func.func_name
|
wrapper.func_name = func.func_name
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ def callQueue(event):
|
||||||
# Rate limit and delay function call if needed, If the function called again within the rate limit interval then previous queued call will be dropped
|
# Rate limit and delay function call if needed, If the function called again within the rate limit interval then previous queued call will be dropped
|
||||||
# Return: Immedietly gevent thread
|
# Return: Immedietly gevent thread
|
||||||
def callAsync(event, allowed_again=10, func=None, *args, **kwargs):
|
def callAsync(event, allowed_again=10, func=None, *args, **kwargs):
|
||||||
if isAllowed(event): # Not called recently, call it now
|
if isAllowed(event, allowed_again): # Not called recently, call it now
|
||||||
called(event)
|
called(event)
|
||||||
# print "Calling now"
|
# print "Calling now"
|
||||||
return gevent.spawn(func, *args, **kwargs)
|
return gevent.spawn(func, *args, **kwargs)
|
||||||
|
|
|
@ -56,7 +56,10 @@ def update():
|
||||||
|
|
||||||
if dest_dir != dest_path.strip("/"):
|
if dest_dir != dest_path.strip("/"):
|
||||||
data = zip.read(inner_path)
|
data = zip.read(inner_path)
|
||||||
open(dest_path, 'wb').write(data)
|
try:
|
||||||
|
open(dest_path, 'wb').write(data)
|
||||||
|
except Exception, err:
|
||||||
|
print dest_path, err
|
||||||
|
|
||||||
print "Done."
|
print "Done."
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,13 @@ def main():
|
||||||
main.start()
|
main.start()
|
||||||
if main.update_after_shutdown: # Updater
|
if main.update_after_shutdown: # Updater
|
||||||
import update, sys, os, gc
|
import update, sys, os, gc
|
||||||
|
# Try cleanup openssl
|
||||||
|
try:
|
||||||
|
if "lib.opensslVerify" in sys.modules:
|
||||||
|
sys.modules["lib.opensslVerify"].opensslVerify.close()
|
||||||
|
except Exception, err:
|
||||||
|
print "Error closing openssl", err
|
||||||
|
|
||||||
# Update
|
# Update
|
||||||
update.update()
|
update.update()
|
||||||
|
|
||||||
|
@ -24,7 +31,7 @@ def main():
|
||||||
except Exception, err: # Prevent closing
|
except Exception, err: # Prevent closing
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raw_input("-- Error happened, press enter to close --")
|
traceback.print_exc(file=open("log/error.log", "a"))
|
||||||
|
|
||||||
if main and main.update_after_shutdown: # Updater
|
if main and main.update_after_shutdown: # Updater
|
||||||
# Restart
|
# Restart
|
||||||
|
|
Loading…
Reference in a new issue