diff --git a/client-webserver/README b/client-webserver/README index 0d34a61a0..728e3c3a1 100644 --- a/client-webserver/README +++ b/client-webserver/README @@ -2,23 +2,6 @@ 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), redis, python-redis, python-protobuf, - python-nss - probably wants to run under a web server like lighttpd with fastcgi +Set CHOCOLATESERVER environment variable for client.py! client.py - experimental tool for making requests and parsing replies - -chocolate_protocol.proto - protocol definition; needs protobuf-compiler - -sni_challenge - - Assumes Apache server with name based virtual hosts is running - (for intended address). - Call perform_sni_cert_challenge(address, r, nonce) to do the whole - challenge. - Example code is given in main method - Right now requires full path specification of CSR/KEY in the Global - Variables (how should this be specified?) diff --git a/client-webserver/CSR.py b/server-ca/CSR.py similarity index 100% rename from client-webserver/CSR.py rename to server-ca/CSR.py diff --git a/client-webserver/Makefile b/server-ca/Makefile similarity index 86% rename from client-webserver/Makefile rename to server-ca/Makefile index d7501a446..91343eef4 100644 --- a/client-webserver/Makefile +++ b/server-ca/Makefile @@ -3,6 +3,7 @@ deploy: chocolate_protocol_pb2.py chocolate.py CSR.py pkcs10.py daemon.py chocolate_protocol_pb2.py: chocolate_protocol.proto protoc chocolate_protocol.proto --python_out=. + cp -p chocolate_protocol_pb2.py ../client-webserver/ clean: rm -f *.pyc diff --git a/server-ca/README b/server-ca/README index 425ea402a..57b0adf0a 100644 --- a/server-ca/README +++ b/server-ca/README @@ -1,2 +1,22 @@ In this directory is a reference CA implementation of the Chocolate protocol, DV and signing mechanism. + +Set CHOCOLATESERVER environment variable for "make deploy"! + + +chocolate.py - server-side, requires web.py (python-webpy), + PyCrypto (python-crypto) 2.3 (not 2.1!!), redis, python-redis, + python-protobuf, python-nss + probably wants to run under a web server like lighttpd with fastcgi + + +chocolate_protocol.proto - protocol definition; needs protobuf-compiler + +sni_challenge - + Assumes Apache server with name based virtual hosts is running + (for intended address). + Call perform_sni_cert_challenge(address, r, nonce) to do the whole + challenge. + Example code is given in main method + Right now requires full path specification of CSR/KEY in the Global + Variables (how should this be specified?) diff --git a/client-webserver/REDIS b/server-ca/REDIS similarity index 100% rename from client-webserver/REDIS rename to server-ca/REDIS diff --git a/client-webserver/chocolate.py b/server-ca/chocolate.py similarity index 96% rename from client-webserver/chocolate.py rename to server-ca/chocolate.py index 6d021a7ad..fc651535f 100755 --- a/client-webserver/chocolate.py +++ b/server-ca/chocolate.py @@ -3,7 +3,8 @@ import web, redis, time import CSR from Crypto.Hash import SHA256, HMAC -from Crypto import Random +from Crypto.PublicKey import RSA +from Crypto import Random from chocolate_protocol_pb2 import chocolatemessage from google.protobuf.message import DecodeError @@ -102,6 +103,10 @@ class session(object): """Has there already been a signing request made in this session?""" return sessions.hget(self.id, "state") is not None + def pubkey(self): + """Return the PEM-formatted subject public key from the CSR.""" + return CSR.pubkey(sessions.hget(self.id, "csr")) + def cert(self): """Return the issued certificate.""" return sessions.hget(self.id, "cert") @@ -276,8 +281,12 @@ class session(object): chall.type = int(c["type"]) chall.name = c["name"] chall.succeeded = (c["satisfied"] == "True") # TODO: this contradicts comment in protocol about meaning of "succeeded" - chall.data.append(c["dvsni:r"]) + # Calculate y + dvsni_r = c["dvsni:r"] + y = RSA.importKey(self.pubkey()).encrypt(dvsni_r, None)[0] + # In dvsni, we send nonce, y, ext chall.data.append(c["dvsni:nonce"]) + chall.data.append(y) chall.data.append(c["dvsni:ext"]) def POST(self): diff --git a/client-webserver/chocolate_protocol.proto b/server-ca/chocolate_protocol.proto similarity index 100% rename from client-webserver/chocolate_protocol.proto rename to server-ca/chocolate_protocol.proto diff --git a/client-webserver/daemon.py b/server-ca/daemon.py similarity index 97% rename from client-webserver/daemon.py rename to server-ca/daemon.py index 4617b25f0..692072309 100644 --- a/client-webserver/daemon.py +++ b/server-ca/daemon.py @@ -26,7 +26,7 @@ # If the client never checks in, the daemon can keep advancing # the request's state, which may not be the right behavior. -import redis, time +import redis, time, CSR r = redis.Redis() from Crypto.Hash import SHA256, HMAC @@ -132,8 +132,10 @@ def issue(session): return # Note that we can push this back into the original queue. # TODO: need to add a way to make sure we don't test the same - # TODO: actually issue the cert - r.hset(session, "cert", "----ISSUED CERT GOES HERE----") + # TODO: actually make this call issue the cert + csr = r.hget(session, "csr") + cert = CSR.issue(csr) + r.hset(session, "cert", cert) if False: # once issuing cert succeeded r.hset(session, "state", "done") r.lpush("pending-done", session) diff --git a/client-webserver/pkcs10.py b/server-ca/pkcs10.py similarity index 100% rename from client-webserver/pkcs10.py rename to server-ca/pkcs10.py diff --git a/server-ca/sni_challenge/verify_sni_challenge.py b/server-ca/sni_challenge/verify_sni_challenge.py index bad782af3..ced6f388f 100644 --- a/server-ca/sni_challenge/verify_sni_challenge.py +++ b/server-ca/sni_challenge/verify_sni_challenge.py @@ -3,6 +3,7 @@ from Crypto import Random import sni_support import hmac import hashlib +import binascii S_SIZE = 32 NONCE_SIZE = 32 @@ -15,6 +16,13 @@ def byteToHex(byteStr): return ''.join(["%02X" % ord(x) for x in byteStr]).strip() def check_challenge_value(ext_value, r): + """ + Checks that a challenge response actually passes the challenge + + ext_value: string returned by client-webserver's X.509 cert + chocolate extension + r: secret random key (binary string) chosen by server-CA + """ s = ext_value[0:S_SIZE] mac = ext_value[S_SIZE:] expected_mac = hmac.new(r, str(s), hashlib.sha256).digest() @@ -30,6 +38,17 @@ def check_challenge_value(ext_value, r): return False def verify_challenge(address, r, nonce): + """ + Verifies an SNI challenge at address (assumes port 443) + + address: string host (e.g. "127.0.0.1") + r: secret random key (binary string) + nonce: ascii string of nonce (e.g. "66f58cfb...") + + returns (result, reason) + result: True/False for passed/failed verification + reason: Human-readable string describing reason for result + """ sni_name = nonce + ".chocolate" context = M2Crypto.SSL.Context() @@ -72,7 +91,7 @@ def main(): r = Random.get_random_bytes(NONCE_SIZE) r = "testValueForR" encryptedValue = testkey.encrypt(r, 0) - valid, response = verify_challenge("127.0.0.1", r, nonce) + valid, response = verify_challenge("127.0.0.1", r, binascii.hexlify(nonce)) print response if __name__ == "__main__":