diff --git a/webserver/Makefile b/webserver/Makefile new file mode 100644 index 000000000..74511a1ba --- /dev/null +++ b/webserver/Makefile @@ -0,0 +1,8 @@ +deploy: chocolate_protocol_pb2.py chocolate.py + scp chocolate_protocol.proto chocolate.py ${CHOCOLATESERVER}: && ssh ${CHOCOLATESERVER} protoc chocolate_protocol.proto --python_out=. + +chocolate_protocol_pb2.py: chocolate_protocol.proto + protoc chocolate_protocol.proto --python_out=. + +clean: + rm -f chocolate_protocol_pb2.pyc diff --git a/webserver/README b/webserver/README index de6f8a644..d322bc518 100644 --- a/webserver/README +++ b/webserver/README @@ -1,2 +1,13 @@ In this directory are tools that will run on webservers for sysadmins to automatically obtain their certs + + +Set CHOCOLATESERVER environment variable for "make deploy" and client.py! + + +chocolate.py - server-side, requires web.py (python-webpy), PyCrypto (python-crypto) + probably wants to run under a web server like lighttpd with fastcgi + +client.py - experimental tool for making requests and parsing replies + +chocolate_protocol.proto - protocol definition; needs protobuf-compiler diff --git a/webserver/chocolate.py b/webserver/chocolate.py new file mode 100755 index 000000000..ab3cfd063 --- /dev/null +++ b/webserver/chocolate.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python + +import web +from Crypto.Hash import SHA256, HMAC +from chocolate_protocol_pb2 import chocolatemessage +from google.protobuf.message import DecodeError + +def hmac(k, m): + return HMAC.new(k, m, SHA256).hexdigest() + +urls = ( + '.*', 'index' +) + +class index: + def GET(self): + web.header("Content-type", "text/html") + return "Hello, world! This server only accepts POST requests." + + def POST(self): + web.header("Content-type", "text/html") + web.setcookie("chocolate", hmac("foo", "bar"), + secure=True) # , httponly=True) + m = chocolatemessage() + r = chocolatemessage() + r.chocolateversion = 1 + try: + m.ParseFromString(web.data()) + except DecodeError: + r.failure.cause = r.BadRequest + else: + if m.chocolateversion != 1: + r.failure.cause = r.UnsupportedVersion + if m.debug: return "SAW MESSAGE: %s\n" % str(r) + else: return r.SerializeToString() + +if __name__ == "__main__": + app = web.application(urls, globals()) + app.run() diff --git a/webserver/chocolate_protocol.proto b/webserver/chocolate_protocol.proto new file mode 100644 index 000000000..965b195fe --- /dev/null +++ b/webserver/chocolate_protocol.proto @@ -0,0 +1,80 @@ +message chocolatemessage { + required int32 chocolateversion = 1; + /* required string session? */ + + message SigningRequest { + required int64 timestamp = 2; + required string nonce = 3; + required string csr = 4; + required string sig = 5; + optional string clientpuzzle = 6; + /* server can specify difficulty? */ + } + + enum FailureReason { + UnsupportedVersion = 0; + AbandonedRequest = 1; + ServerOutage = 2; + ServerGone = 3; + StaleRequest = 4; + BadSignature = 5; + BadCSR = 6; + BadRequest = 7; + /* Unauthenticated = ?; */ + NeedClientPuzzle = 8; + CannotIssueThatName = 9; + UnsafeKey = 10; + ChallengeFailed = 11; + ChallengeTimeout = 12; + } + + message Failure { + required FailureReason cause = 1; + optional string URI = 2; + /* reference to which SigningRequest this relates to? */ + } + + message Proceed { + /* possibly URI should be replaced with session ID? */ + required string timestamp = 1; + required string URI = 2; + optional int32 polldelay = 3; + } + + enum ChallengeType { + DomainValidate = 0; + EmailValidate = 1; + Payment = 2; + } + + message Challenge { + required ChallengeType type = 1; + required int32 challengeid = 2; + optional string name = 3; + optional string data = 4; + optional string URI = 5; + optional bool succeeded = 6; + /* from server: true if server ACK success, + false if server NAK success, + omit if server doesn't know if client + has attempted yet. + + from client: true if claiming to be done, + false if unable, + omit if client hasn't attempted yet. */ + } + + message Success { + required string certificate = 1; /* Repeated string certificate? */ + } + + optional SigningRequest request = 2; + optional Failure failure = 3; + optional Proceed proceed = 4; + repeated Challenge challenge = 5; + repeated Challenge completedchallenge = 6; + optional Success success = 7; + + optional bool debug = 8; /* Causes server to return text instead of + message! */ +} diff --git a/webserver/client.py b/webserver/client.py new file mode 100644 index 000000000..603cc8ddb --- /dev/null +++ b/webserver/client.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +from chocolate_protocol_pb2 import chocolatemessage +import urllib2, os, sys + +try: + upstream = "https://%s/chocolate.py" % os.environ["CHOCOLATESERVER"] +except KeyError: + print "Please set the environment variable CHOCOLATESERVER to the hostname" + print "of a server that speaks this protocol." + sys.exit(1) + +def do(m): + u = urllib2.urlopen(upstream, m.SerializeToString()) + return u.read() + +def decode(m): + return str(chocolatemessage.FromString(m)) + +m = chocolatemessage()