mirror of
https://github.com/certbot/certbot.git
synced 2026-06-06 07:12:54 -04:00
som eminor changes, fixes, and reorganization
This commit is contained in:
parent
12d8d34672
commit
5fb6a5b07d
9 changed files with 9 additions and 333 deletions
|
|
@ -28,4 +28,5 @@ hashcash_expiry = 60*60
|
|||
extra_name_blacklist = ["eff.org", "www.eff.org"]
|
||||
|
||||
# Name of file containing cert chain
|
||||
cert_chain_file = "chain.pem"
|
||||
cert_chain_file = "demoCA/cacert.pem"
|
||||
debug = True
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import web, redis, time, binascii, re, urllib2
|
||||
import CSR
|
||||
from redis_lock import redis_lock
|
||||
from trustify.protocol import hashcash
|
||||
from CSR import M2Crypto
|
||||
from Crypto import Random
|
||||
|
|
@ -162,7 +163,7 @@ class session(object):
|
|||
"""Is the hashcash string h valid for a request to this server for
|
||||
signing n names?"""
|
||||
if hashcash.check(stamp=h, resource=chocolate_server_name, \
|
||||
bits=difficulty*n, check_expiration=hashcash_expiry):
|
||||
bits=difficulty, check_expiration=hashcash_expiry):
|
||||
# sessions.sadd returns True upon adding to a set and
|
||||
# False if the item was already in the set.
|
||||
return sessions.sadd("spent-hashcash", h)
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# functions common to the various kinds of daemon
|
||||
|
||||
# TODO: define a log function that sends a pubsub message to the
|
||||
# logger daemon
|
||||
|
||||
import time, binascii
|
||||
from Crypto import Random
|
||||
|
||||
def signal_handler(a, b):
|
||||
global clean_shutdown
|
||||
clean_shutdown = True
|
||||
r.publish("exit", "clean-exit")
|
||||
r.lpush("exit", "clean-exit")
|
||||
|
||||
def short(session):
|
||||
"""Return the first 12 bytes of a session ID, or, for a
|
||||
challenge ID, the challenge ID with the session ID truncated."""
|
||||
tmp = session.partition(":")
|
||||
return tmp[0][:12] + "..." + tmp[1] + tmp[2]
|
||||
|
||||
def ancient(session, state):
|
||||
"""Given that this session is in the specified named state,
|
||||
decide whether the daemon should forcibly expire it for being too
|
||||
old, even if no client request has caused the serve to mark the
|
||||
session as expired. This is most relevant to truly abandoned
|
||||
sessions that no client ever asks about."""
|
||||
age = int(time.time()) - int(r.hget(session, "created"))
|
||||
if state == "makechallenge" and age > 120:
|
||||
if debug: print "considered", short(session), "ancient"
|
||||
return True
|
||||
if state == "testchallenge" and age > 600:
|
||||
if debug: print "considered", short(session), "ancient"
|
||||
return True
|
||||
return False
|
||||
|
||||
def random():
|
||||
"""Return 64 hex digits representing a new 32-byte random number."""
|
||||
return binascii.hexlify(Random.get_random_bytes(32))
|
||||
|
||||
def random_raw():
|
||||
"""Return 32 random bytes."""
|
||||
return Random.get_random_bytes(32)
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# This daemon runs on the CA side to look for requests in
|
||||
# the database that are waiting for a cert to be issued.
|
||||
|
||||
import redis, redis_lock, CSR, sys, signal
|
||||
from sni_challenge.verify import verify_challenge
|
||||
from Crypto import Random
|
||||
|
||||
r = redis.Redis()
|
||||
ps = r.pubsub()
|
||||
issue_lock = redis_lock.redis_lock(r, "issue_lock")
|
||||
# This lock guards the ability to issue certificates with "openssl ca",
|
||||
# which has no locking of its own. We don't need locking for the updates
|
||||
# that the daemon performs on the sessions in the database because the
|
||||
# queues pending-makechallenge, pending-testchallenge, and pending-issue
|
||||
# are updated atomically and the daemon only ever acts on sessions that it
|
||||
# has removed from a queue.
|
||||
|
||||
debug = "debug" in sys.argv
|
||||
clean_shutdown = False
|
||||
|
||||
from daemon_common import signal_handler, short, ancient, random, random_raw
|
||||
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
def issue(session):
|
||||
if r.hget(session, "live") != "True":
|
||||
# This session has died due to some other reason, like an
|
||||
# illegal request or timeout, since it entered testchallenge
|
||||
# state. Consequently, we're not allowed to advance its
|
||||
# state any further, and it should be removed from the
|
||||
# pending-requests queue and not pushed into any other queue.
|
||||
# We don't have to remove it from pending-testchallenge
|
||||
# because the caller has already done so.
|
||||
#
|
||||
# Having a session in pending-issue die is a very weird case
|
||||
# that probably suggests that timeouts are set incorrectly
|
||||
# or that the client is misbehaving very badly. This means
|
||||
# that a request passed all of its challenges but the
|
||||
# session nonetheless died for some reason unrelated to failing
|
||||
# challenges before the cert could be issued. Normally, this
|
||||
# should never happen.
|
||||
if debug: print "removing expired (issue-state!?) session", short(session)
|
||||
r.lrem("pending-requests", session)
|
||||
return
|
||||
if r.hget(session, "state") != "issue":
|
||||
return
|
||||
csr = r.hget(session, "csr")
|
||||
names = r.lrange("%s:names" % session, 0, -1)
|
||||
with issue_lock:
|
||||
cert = CSR.issue(csr, names)
|
||||
r.hset(session, "cert", cert)
|
||||
if cert: # once issuing cert succeeded
|
||||
if debug: print "%s: issued certificate for names: %s" % (short(session), ", ".join(names))
|
||||
r.hset(session, "state", "done")
|
||||
# r.lpush("pending-done", session)
|
||||
else: # should not be reached in deployed version
|
||||
if debug: print "issuing for", short(session), "failed"
|
||||
r.lpush("pending-issue", session)
|
||||
|
||||
while True:
|
||||
(where, what) = r.brpop(["exit", "pending-issue"])
|
||||
if where == "exit":
|
||||
r.lpush("exit", "exit")
|
||||
break
|
||||
elif where == "pending-issue":
|
||||
issue(what)
|
||||
if clean_shutdown:
|
||||
print "daemon exiting cleanly"
|
||||
break
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# This daemon runs on the CA side to handle logging.
|
||||
|
||||
import redis, signal
|
||||
|
||||
r = redis.Redis()
|
||||
ps = r.pubsub()
|
||||
|
||||
debug = "debug" in sys.argv
|
||||
clean_shutdown = False
|
||||
|
||||
from daemon_common import signal_handler
|
||||
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
ps.subscribe(["logs", "exit"])
|
||||
for message in ps.listen():
|
||||
if message["type"] != "message":
|
||||
continue
|
||||
if message["channel"] == "logs":
|
||||
if debug: print message["data"]
|
||||
continue
|
||||
if message["channel"] == "exit":
|
||||
break
|
||||
if clean_shutdown:
|
||||
print "daemon exiting cleanly"
|
||||
break
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# This daemon runs on the CA side to look for requests in
|
||||
# the database that are waiting for challenges to be issued.
|
||||
|
||||
import redis, redis_lock, time, sys, signal
|
||||
|
||||
r = redis.Redis()
|
||||
ps = r.pubsub()
|
||||
|
||||
debug = "debug" in sys.argv
|
||||
clean_shutdown = False
|
||||
|
||||
from daemon_common import signal_handler, short, ancient, random, random_raw
|
||||
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
def makechallenge(session):
|
||||
if r.hget(session, "live") != "True":
|
||||
# This session has died due to some other reason, like an
|
||||
# illegal request or timeout, since it entered makechallenge
|
||||
# state. Consequently, we're not allowed to advance its
|
||||
# state any further, and it should be removed from the
|
||||
# pending-requests queue and not pushed into any other queue.
|
||||
# We don't have to remove it from pending-makechallenge
|
||||
# because the caller has already done so.
|
||||
if debug: print "removing expired session", short(session)
|
||||
r.lrem("pending-requests", session)
|
||||
return
|
||||
# Currently only makes challenges of type 0 (DomainValidateSNI)
|
||||
# This challenge type has three internal data parameters:
|
||||
# dvsni:nonce, dvsni:r, dvsni:ext
|
||||
# This challenge type sends three data parameters to the client:
|
||||
# nonce, y = E(r), ext
|
||||
#
|
||||
# Make one challenge for each name. (This one-to-one relationship
|
||||
# is not an inherent protocol requirement!)
|
||||
names = r.lrange("%s:names" % session, 0, -1)
|
||||
if debug: print "%s: new valid request" % session
|
||||
if debug: print "%s: from requesting client at %s" % (short(session), r.hget(session, "client-addr"))
|
||||
if debug: print "%s: for %d names: %s" % (short(session), len(names), ", ".join(names))
|
||||
for i, name in enumerate(names):
|
||||
challenge = "%s:%d" % (session, i)
|
||||
r.hset(challenge, "challtime", int(time.time()))
|
||||
r.hset(challenge, "type", 0) # DomainValidateSNI
|
||||
r.hset(challenge, "name", name)
|
||||
r.hset(challenge, "satisfied", False)
|
||||
r.hset(challenge, "failed", False)
|
||||
r.hset(challenge, "dvsni:nonce", random())
|
||||
r.hset(challenge, "dvsni:r", random_raw())
|
||||
r.hset(challenge, "dvsni:ext", "1.3.3.7")
|
||||
# Keep accurate count of how many challenges exist in this session.
|
||||
r.hincrby(session, "challenges", 1)
|
||||
if debug: print "created new challenge", short(challenge)
|
||||
if True: # challenges have been created
|
||||
r.hset(session, "state", "testchallenge")
|
||||
else:
|
||||
r.lpush("pending-makechallenge", session)
|
||||
|
||||
while True:
|
||||
(where, what) = r.brpop(["exit", "pending-makechallenge"])
|
||||
if where == "exit":
|
||||
r.lpush("exit", "exit")
|
||||
break
|
||||
elif where == "pending-makechallenge":
|
||||
makechallenge(what)
|
||||
if clean_shutdown:
|
||||
print "daemon exiting cleanly"
|
||||
break
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# This daemon runs on the CA side to look for requests in
|
||||
# the database that are waiting for the CA to test whether
|
||||
# challenges have been met, and to perform this test.
|
||||
|
||||
import redis, redis_lock, time, sys, signal
|
||||
from sni_challenge.verify import verify_challenge
|
||||
|
||||
r = redis.Redis()
|
||||
ps = r.pubsub()
|
||||
|
||||
debug = "debug" in sys.argv
|
||||
clean_shutdown = False
|
||||
|
||||
from daemon_common import signal_handler, short, ancient, random, random_raw
|
||||
|
||||
def signal_handler(a, b):
|
||||
global clean_shutdown
|
||||
clean_shutdown = True
|
||||
r.publish("exit", "clean-exit")
|
||||
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
def testchallenge(session):
|
||||
if r.hget(session, "live") != "True":
|
||||
# This session has died due to some other reason, like an
|
||||
# illegal request or timeout, since it entered testchallenge
|
||||
# state. Consequently, we're not allowed to advance its
|
||||
# state any further, and it should be removed from the
|
||||
# pending-requests queue and not pushed into any other queue.
|
||||
# We don't have to remove it from pending-testchallenge
|
||||
# because the caller has already done so.
|
||||
if debug: print "removing expired session", short(session)
|
||||
r.lrem("pending-requests", session)
|
||||
return
|
||||
if r.hget(session, "state") != "testchallenge":
|
||||
return
|
||||
if int(r.hincrby(session, "times-tested", 1)) > 3:
|
||||
# This session has already been unsuccessfully tested three
|
||||
# times. Clearly, something has gone wrong or the client is
|
||||
# just trying to annoy us. Do not allow it to be tested again.
|
||||
r.hset(session, "live", False)
|
||||
r.lrem("pending-requests", session)
|
||||
return
|
||||
all_satisfied = True
|
||||
for i, name in enumerate(r.lrange("%s:names" % session, 0, -1)):
|
||||
challenge = "%s:%d" % (session, i)
|
||||
if debug: print "testing challenge", short(challenge)
|
||||
challtime = int(r.hget(challenge, "challtime"))
|
||||
challtype = int(r.hget(challenge, "type"))
|
||||
name = r.hget(challenge, "name")
|
||||
satisfied = r.hget(challenge, "satisfied") == "True"
|
||||
failed = r.hget(challenge, "failed") == "True"
|
||||
# TODO: check whether this challenge is too old
|
||||
if not satisfied and not failed:
|
||||
# if debug: print "challenge", short(challenge), "being tested"
|
||||
if challtype == 0: # DomainValidateSNI
|
||||
if debug: print "\tbeginning dvsni test to %s" % name
|
||||
dvsni_nonce = r.hget(challenge, "dvsni:nonce")
|
||||
dvsni_r = r.hget(challenge, "dvsni:r")
|
||||
dvsni_ext = r.hget(challenge, "dvsni:ext")
|
||||
direct_result, direct_reason = verify_challenge(name, dvsni_r, dvsni_nonce, False)
|
||||
proxy_result, proxy_reason = verify_challenge(name, dvsni_r, dvsni_nonce, True)
|
||||
if debug:
|
||||
print "\t...direct probe: %s (%s)" % (direct_result, direct_reason)
|
||||
print "\tTor proxy probe: %s (%s)" % (proxy_result, proxy_reason)
|
||||
if direct_result and proxy_result:
|
||||
r.hset(challenge, "satisfied", True)
|
||||
else:
|
||||
all_satisfied = False
|
||||
# TODO: distinguish permanent and temporarily failures
|
||||
# can cause a permanent failure under some conditions, causing
|
||||
# the session to become dead. TODO: need to articulate what
|
||||
# those conditions are
|
||||
else:
|
||||
# Don't know how to handle this challenge type
|
||||
all_satisfied = False
|
||||
elif not satisfied:
|
||||
if debug: print "\tchallenge was not attempted"
|
||||
all_satisfied = False
|
||||
if all_satisfied:
|
||||
# Challenges all succeeded, so we should prepare to issue
|
||||
# the requested cert.
|
||||
# TODO: double-check that there were > 0 challenges,
|
||||
# so that we don't somehow mistakenly issue a cert in
|
||||
# response to an empty list of challenges (even though
|
||||
# the daemon that put this session on the queue should
|
||||
# also have implicitly guaranteed this).
|
||||
if debug: print "\t** All challenges satisfied; request %s GRANTED" % short(session)
|
||||
r.hset(session, "state", "issue")
|
||||
r.lpush("pending-issue", session)
|
||||
else:
|
||||
# Some challenges were not verified. In the current
|
||||
# design of this daemon, the client must contact
|
||||
# us again to request that the session be placed back
|
||||
# in pending-testchallenge!
|
||||
pass
|
||||
|
||||
while True:
|
||||
(where, what) = r.brpop(["exit", "pending-testchallenge"])
|
||||
if where == "exit":
|
||||
r.lpush("exit", "exit")
|
||||
break
|
||||
elif where == "pending-testchallenge":
|
||||
with redis_lock(r, "lock-" + what):
|
||||
testchallenge(what)
|
||||
if clean_shutdown:
|
||||
print "daemon exiting cleanly"
|
||||
break
|
||||
|
|
@ -68,8 +68,8 @@ def verify_challenge(address, r, nonce, socksify=False):
|
|||
sni_support.set_sni_ext(conn.ssl, sni_name)
|
||||
try:
|
||||
conn.connect((address, 443))
|
||||
except:
|
||||
return False, "Connection to SSL Server failed"
|
||||
except Exception, e:
|
||||
return False, "Connection to SSL Server failed (%s)" % str(e)
|
||||
|
||||
cert_chain = conn.get_peer_cert_chain()
|
||||
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ nohup ./logging-daemon.py &
|
|||
# runs of the daemon should occur here.
|
||||
|
||||
echo "Starting issue daemon..."
|
||||
nohup daemons/issue-daemon.py &
|
||||
nohup ./issue-daemon.py &
|
||||
|
||||
for instance in a b c
|
||||
do
|
||||
echo "Starting testchallenge daemon $instance..."
|
||||
nohup daemons/testchallenge-daemon.py &
|
||||
nohup ./testchallenge-daemon.py &
|
||||
done
|
||||
|
||||
echo "Starting makechallenge daemon..."
|
||||
nohup daemons/makechallenge-daemon.py &
|
||||
nohup ./makechallenge-daemon.py &
|
||||
|
|
|
|||
Loading…
Reference in a new issue