1
0
mirror of https://github.com/certbot/certbot.git synced 2026-01-24 19:22:07 +03:00

All things in the attic are now in the legacy_protocol branch

This commit is contained in:
James Kasten
2014-11-18 18:45:58 -08:00
parent 02bd284390
commit dcdbd13674
70 changed files with 0 additions and 24396 deletions

6
.gitmodules vendored
View File

@@ -1,6 +0,0 @@
[submodule "m3crypto"]
path = attic/m3crypto
url = git@github.com:research/m3crypto.git
[submodule "pygeoip"]
path = attic/pygeoip
url = https://github.com/appliedsec/pygeoip.git

View File

@@ -1,31 +0,0 @@
The attic contains code and documentation about the trustify protocol, an
alternative method for client webservers to request certificates from a server
CA. Lets Encrypt does not plan to speak this protocol, though some of the
things here may be of future use.
Notes on this code:
The Chocolate project to implement sweet automatic encryption for webservers.
There are two portions to the Chocolate protocol.
trustify/ contains code that can be run on any webserver (eventually,
email, XMPP and other SSL-securable servers too); it is used to automatically
request and install a CA-signed certificate for that server's public names.
server-ca/ contains a reference implementation for CAs to receive requests for
certs, set challenges for the requesting servers to prove that they really
control the names, and issue certificates.
Debian dependencies:
build deps:
swig
protobuf-compiler
python-dev
others:
gnutls-bin # for make cert requests
python-protobuf
python-dialog
hashcash

View File

@@ -1,266 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<title>/26$6</title>
</head>
<body><b>ABSTRACT</b><br
/><br
/>This protocol exists to facilitate the activation of secure TLS encryption on all Internet servers to which it is applicable, in a manner compatible with existing deployed client bases, but without the need for the expert human configuration that has limited the use of TLS to date.&nbsp; The protocol provides transmission and validation of certification requests, and the issuance of digital certificates, with as little human intervention as possible.<br
/><br
/><br
/><b>INTRODUCTION</b><br
/><br
/>The Trustify (aka Chocolate) protocol is used between a <b>client</b>, which is a software package on an Internet-connected host that is or will be used to operate a Web server or other TLS service, and a <b>server</b>, which is a certification service provided by a certificate authority.<br
/><br
/>The protocol is implemented as a series of Protocol Buffers messages which are serialized and transported as HTTP POST request and response bodies relative to an HTTPS URI published by the server. Currently, each client to server message is a new HTTP POST and each server reply is a new response body.&nbsp; Hence, the client and server alternate sending messages; asynchronous notification of changes in server state do not occur, but the client can be instructed to poll periodically while the server is working.&nbsp; Full specification of the Trustify message format is in the Messages section below.<br
/><br
/>The client must verify the server's own certificate when making each HTTPS connection.&nbsp; If the client cannot establish a secure connection to the server, the client SHOULD abort the session without any further communication.<br
/><br
/>Because avoiding misissuance of certificates is a fundamental priority, the server is able to abort sessions at any time for a wide variety of reasons, declining any further communication about an abandoned session.<br
/><br
/><b>IDENTIFYING AVAILABLE TRUSTIFY SERVERS</b><br
/><br
/>The first step for a Trustify client is to identify the currently operating and available Trustify servers that correspond to CAs issuing certificates through the protocol.&nbsp; Clients contain an HTTPS URL that always refers to a fresh client list.&nbsp; For reliability purposes, if the URL cannot be fetched, clients may refer to a previously cached copy of the list.<br
/><br
/>The Trustify Server List will contain the following data:<br
/><br
/>[Server name string, server URL, &lt;optional&gt; [policy string1, policy string 2,..]]<br
/><br
/>The Server name string is a UTF-8 string containing an English readable name (possibly followed by a native name in another alphabet) of the CA.&nbsp; The server URL is an HTTPS endpoint that responds to the Trustify protocol as documented below.&nbsp; The policy strings are an optional list of parameters that may at some point express relevant comparison points between multiple CAs.&nbsp; The semantics of policy fields will be specified by the Trustify governance foundation.<br
/><br
/><b>SESSIONS</b><br
/><br
/>Trustify sessions are distinguished by a random session identifier which is issued by the server in its very first response to a new client connection. The session identifier must then be mentioned in all communications in either direction in order to associate requests and responses with a particular transaction. Omitting the session identifier is treated as a request to begin a new session, while messages containing an unrecognized or stale session identifier are rejected.<br
/><br
/>The session identifier is an ephemeral authenticator private to the client and server.&nbsp; An entity that knows the session identifier is able to make requests to the server related to the session, including erroneous requests that will cause the session to be terminated. Session identifiers from old, inactive sessions are explicitly marked invalid and no further use can be made of them.<br
/><br
/>The goal of a session is the issuance of a new digital certificate. The client must already possess a subject public key and a list of requested subject host names.&nbsp; The client begins the protocol with the goal of obtaining a certificate issued by the server, associating the public key with each subject name.<br
/><br
/><b>MESSAGE</b><br
/><br
/>The Trusitfy message object contains a version ID and a session ID.&nbsp; It also contains exactly one of the following specific message objects (except for challenge and completedchallenge):<br
/><br
/>request [type SigningRequest]: sent exactly once by the client at the beginning of a session in order to request a certificate.&nbsp; The contents indicate the details of the certificate that is being requested in the current session.<br
/><br
/>failure [type Failure]: sent by the client or the server to indicate a reason why the session cannot continue.&nbsp; All failure messages are fatal and result in the server marking the session as expired, so that no further activity may occur.&nbsp; (If a client sends a message related to an expired session, the server will respond with a failure message indicating that the session is expired.)<br
/><br
/>proceed [type Proceed]: sent by the server to request the client to poll again on the current session after a specified time delay.&nbsp; Normally used when the server believes that it is in the process of testing whether challenges have been satisfied or in the process of issuing the requested certificate.<br
/>Note: Protocol also specifies the client can send this message after a polling period.<br
/><br
/>challenge [type Challenge]: sent by the server to announce challenges for the first time or to tell the client whether the server believes that the client has successfully completed a subset of the previously issued challenges.<br
/><br
/>completedchallenge [type Challenge]: sent by the client to announce that the client believes it has successfully completed a challenge.<br
/><br
/>success [type Success]: sent by the server to issue the requested certificate.<br
/><br
/><b>SEQUENCE OF COMMUNICATIONS</b><br
/><br
/><b><i>TODO</i></b>: Document difference between challenge types that call for proceed message and challenge types that call for completedchallenge message.<br
/><br
/>Initial request<br
/>At the start of communications:<br
/><ul><li>1. The client sends a <b>request</b> message, which includes a signing request.</li
><li>2. The server issues a new session ID, which is included with all subsequent messages sent in either direction.</li
><li>3. The server validates the request.&nbsp; If it is known to be invalid or unacceptable for any reason, the server sends a failure message and aborts the protocol.<br/><br
/></li></ul
>Server challenge or certificate issuance<br
/>In response to the <b>request</b> message:<br
/><ul><li>4. Otherwise, in accordance with its policy, the server sends a <b>challenge</b> or <b>success</b> message.&nbsp; (The normal case is a <b>challenge</b> message because the server typically requires proof from the client that the client is authorized to obtain the requested certificate.)</li
><li>5. If the client receives a <b>challenge</b> message, it notes the challenges that it is expected to complete.&nbsp; The client then attempts to complete each specified challenge.</li
><li>6. When the client believes that it has completed a challenge, it can send a <b>completedchallenge</b> message updating the server on its progress. If the client believes that it is unable to complete a challenge, it can send a failure message admitting failure, which will result in the termination of the session.<br/><br
/></li></ul
>Server response to challenge progress<br
/>In response to the client-side <b>completedchallenge</b> or <b>proceed</b> message:<br
/><ul><li>7. If the server observes or agrees that all challenges issued in the session have been completed, it may issue the certificate and send a <b>success</b> message. This terminates the session.</li
><li>8. If the server believes that more time is required for certificate issuance or for challenge verification, it may send a <b>proceed</b> message asking the client to poll again after a specified time interval. The client should poll after this interval by sending completedchallenge (if it has new successes to claim) or proceed (if not). [Return to step 7.]</li
><li>9. If the server believes that one or more challenges have still not been completed, it may send a <b>challenge</b> message indicating the status of the challenges whose completeness has or has not been verified. [Return to step 5.]</li
><li>10. If the server believes that more challenges should be completed, it may send a new <b>challenge</b> message presenting the additional challenges. [Return to step 5.]</li
><li>11. If the server believes that too much time has elapsed or that some challenge was abandoned or has become impossible to satisfy, it can send a <b>failure</b> message. This terminates the session.<br/><br
/></li></ul
><b>VERIFICATION CHALLENGES</b><br
/><br
/>During the course of a session, the Trustify server presents the client with one or more <b>challenges</b>,&nbsp; which are messages specifying tasks that must be completed to verify&nbsp; the preconditions of issuance for the certificate(s) that the client has requested.&nbsp; Challenges are an abstraction layer to allow the Trustify protocol to be enhanced and expanded over time.<br
/><br
/>Several challenges may be presented at once, but further or additional&nbsp; challenges may be presented after previous sets, possibly as a result of&nbsp; information that the server obtained while verifying the earlier&nbsp; challenges.<br
/><br
/>Clients may decide to meet all of the outstanding challenges at once, or may&nbsp; decide to send responses to the challenges one at a time. The semantic&nbsp; relationship between the outstanding challenges may be conjunctive (all challenges must eventually be met before issuance), disjunctive (only one or a subset of challenges must be met), or variable (the set of challenges that are required depends on the manner in which the earlier ones are completed)<br
/><br
/>As&nbsp; a matter of protocol synchronization, there are two subtypes of&nbsp; challenges: those where completion of the challenge is signaled by the client and then verified by the server (such as the DVSNI challenge&nbsp; described below, which requires the client to configure a TLS server in a particular way) and those where completion of the challenge is identified and verified solely by the server (such as a challenge involving a payment or other organizational validation for a high-value domain).<br
/><br
/><b>DVSNI Challenges:</b><br
/><br
/>DVSNI challenges are the fundamental method employed by the Trustify protocol to ensure that clients control&nbsp; the DNS names for which they are requesting certificates.&nbsp; It is intended to be more automatable (and in some cases, more secure) than&nbsp; the email receipt verification that is commonly used by DV CAs, and&nbsp; categorically more secure than the HTTP nonce deployment verification used by some DV CAs.<br
/><br
/>DVSNI requires the client to demonstrate significant administrative control&nbsp; of the domain by not only changing responses from an HTTP server, but by&nbsp; altering the TLS configuration of an HTTPS server to answer specially crafted SNI (Server Name Indication) requests. This shows that the client at least has administrative control of the DNS name's web server&nbsp; software, and probably has full administrative control of the servers that the DNS name points to.<br
/><br
/><b>DVSNI implementation specifics:</b><br
/><br
/><i>&nbsp;&nbsp;&nbsp; Shared parameters (chosen and sent by CA in the Challenge message):</i><br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ${nonce}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A randomly chosen nonce hex output of digest of (random 32-bytes)<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ${y} [:= E(r)]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; r is a random secret (32 bytes, raw binary)<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ${ext}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; x.509 extension (format? e.g: 1.3.3.7)<br
/><br
/><i>&nbsp;&nbsp;&nbsp; Client setup:</i><br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Setup SNI for <i>${nonce}</i>.chocolate<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Serve a self-signed certificate with (critical(?)) X.509 extension <i>${ext}</i>,<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; with value z = HMAC_r(s) || s (random s, r = D(${y}) (private key decrypt))<br
/><br
/><i>&nbsp;&nbsp;&nbsp; Chocolate Server (CA) verification steps:</i><br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Connect to domain.com, with TLS SNI ${nonce}.chocolate<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Check certificate for X.509 extension ${ext}. Verify that value z is formed as expected.<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<i>&nbsp;&nbsp;&nbsp; Purpose:</i><br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Proves that whoever has access to the corresponding private key also has the ability<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; to serve (change configuration for) an arbitrary certificate for an arbitrary<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; subdomain under the desired domain name.<br
/><br
/>&nbsp;&nbsp;&nbsp; <i>Explanation:</i><br
/>Using ${nonce}.chocolate&nbsp; (instead of domain.com) allows a currently running web server to&nbsp; continue serving domain.com without interruption. (Note we still use the&nbsp; IP for domain.com when we connect to the server)&nbsp;&nbsp; It is of slight importance that the .chocolate TLD is not real; it may&nbsp; protect against elaborate attacks against domains like dyndns.com which&nbsp; would allow attackers to control nonce.dyndns.com.&nbsp; If such a domain&nbsp; were also hosted on a virtual hosting service/CDN with the same IP as&nbsp; the attacker, and which really did SNI, it would be important that&nbsp; .chocolate not be real.<br
/>We&nbsp; require the certificate to be modified to ensure that it is freshly&nbsp; generated for this purpose, and not just being served a wildcard&nbsp; certificate. We could do this several ways, but it is necessary to tie&nbsp; this modification to control of the private key (hence the need for z)&nbsp; and to the current Chocolate request (hence the need for ${ext}).&nbsp; Otherwise, a "rogue CA" could receive a request from domain.com, forward&nbsp; it to a "real CA" with a different public key, and have the real&nbsp; domain.com carry out this challenge (which real CA would verify, and&nbsp; give rogue CA a signed cert for domain.com).<br
/><br
/>&nbsp;&nbsp;&nbsp; <i>Deviations from model:</i><br
/>&nbsp;&nbsp;&nbsp;&nbsp; SNI only allows multiplexing over the domain.&nbsp; Apache always has a&nbsp; default SNI page... even if you provide a completely wrong header.&nbsp; The&nbsp; real challenge happens with y and z.<br
/>&nbsp;&nbsp;&nbsp;&nbsp; Apache uses the first virtual server specified on the ip/port to act as a&nbsp; default server if the SNI extension is specified by the client but does&nbsp; not match any of the virtual servers.<br
/><br
/><b>DVSNI Security Considerations:</b><br
/><br
/>Shared hosting environments with multiple DNS names pointed a single host and IP are a common situation on the modern Internet.&nbsp; We expect that the hosting provider managing these guest domains should take its own steps&nbsp; to prevent one from listening on port 443 in an unconstrained manner for the other guests' domains.&nbsp; If it does not, the DVSNI verification step&nbsp; will be unable to distinguish the owner of the domain from others who can listen on the domain's privileged ports, and, depending on what&nbsp; other verification steps are applicable, may issue certificates to these other parties.<br
/><br
/>Verification&nbsp; of control on TLS ports other than 443 (such as the TLS email serivces&nbsp; on ports 465, 993 and 995) in the presence of virtual hosting presents similar but possibly more serious challenges, since use of these ports on virtual private servers may be rarer, and policies about them more&nbsp; varied.<br
/><br
/><b>Proof-of-possession challenges:</b><br
/><br
/>The&nbsp; server MUST check whether there are pre-existing valid certificates for the requested DNS names by consulting Certificate Transparency logs and/or the EFF SSL Observatory.&nbsp; If such certificates exist, the server SHOULD NOT proceed unless it can ensure that issuance will not reduce the security of TLS services deployed with those existing certificates.<br
/><br
/>Traditional OV verification processes may be one way of achieving this, but it is recommended that a Proof of Possession challenge be offered as an alternative.&nbsp; This challenge type requires&nbsp; the client to sign a challenge nonce using the private key from one of the existing valid certificates for the DNS name in question.<br
/><br
/><br
/><b>Payment challenges:</b><br
/><br
/>In&nbsp; limited situations, it may be determined that traditional OV or EV processes are required for the issuance of certificates for high-value, high-traffic DNS names.&nbsp; In such cases a payment challenge may be used to facilitate the transition to the OV or EV process at the client end. The Trustify governance foundation may or may not decide to allow such challenges, but if it does they will&nbsp; be in limited circumstances, where the OV or EV process significant enhances the security of the domain in question, and not a common case for Trustify protocol execution.<br
/><br
/>If Payment challenges exist, they will simply contain a URL for a web page that can specify and accept the payment required.<br
/><br
/><br
/><b>FAILURE REASONS</b><br
/><br
/>Currently, the following reasons for the failure of a session are defined:<br
/><br
/><b>UnsupportedVersion</b>: the requested protocol version is not available.<br
/><b>AbandonedRequest</b>: the client has abandoned the session and is no longer requesting issuance of the certificate.<br
/><b>ServerOutage</b>: the service is temporarily unavailable.<br
/><b>ServerGone</b>: the service is permanently unavailable.<br
/><b>StaleRequest</b>: the request is expired as a result of its age, excessive delay in the client-server interaction, or because the request has previously failed for another reason.<br
/><b>BadSignature</b>: the digital signature used by the client to prove its possession of the private key corresponding to the subject public key is invalid.<br
/><b>BadCSR</b>: the certificate signing request sent by the client is invalid in some way.<br
/><b>BadRequest</b>: the subject public key, one or more subject host names, or some other aspect of the signing request is invalid.<br
/><b>NeedClientPuzzle</b>: as a denial-of-service mitigation measure, the server cannot accept the request without additional proof of work by the client.<br
/><b>CannotIssueThatName</b>: the issuance of a certificate for one or more of the subject host names is administratively prohibited.<br
/><b>ExistingCertificate</b>: a previous certificate for one or more of the subject host names is known to exist and the CA policy does not permit the automated issuance of a new one in response to the current request.<br
/><b>UnsafeKey</b>: the subject public key violates a CA policy or is known to be insecure.<br
/><b>ChallengeFailed</b>: the client's attempt to comply with a challenge was unsuccessful.<br
/><b>ChallengeTimeout</b>: the client did not appear to comply with a challenge within the required period of time.<br
/><br
/>A message of type Failure must contain one of these reasons, and may contain a URI with additional human-readable information about the reason for the failure of the request.<br
/><br
/><b>SECURITY CONSIDERATIONS</b><br
/><br
/>Because the intended application of this protocol causes valuable digital certificates to be issued automatically with no time delay and without human intervention, attackers are likely to be interested in trying to use this protocol to request certificates fraudulently.&nbsp; It should be possible to implement the protocol and verification steps in such a way that the system as a whole is more secure than some existing certificate authority verification processes.<br
/><br
/>Before issuing a certificate, the server needs to perform a large number of validation and policy enforcement steps which are outside the scope of this protocol.&nbsp; For example, the server SHOULD check that the RSA modulus of the submitted subject public key is &gt;=2048 bits and that it is not on any weak RSA modulus blacklist.&nbsp; The server SHOULD check that no CAA records forbid it from issuing a certificate for this domain and that the subject domain is not a high-value domain for which automated certificate issuance should be prevented.&nbsp; The server SHOULD check whether there is any known existing valid certificate for the requested domain and determine under what conditions server policy permits the issuance of a new certificate with concurrent validity.<br
/><br
/>When using DVSNI validation, the server SHOULD perform the probe connection from multiple locations on the Internet to achieve geographic and network topology diversity to reduce the risk that an attacker performs a DNS or BGP attack to appear to control a particular web server.<br
/><br
/>The server SHOULD avoid passing unvalidated or unsanitized data from the client to any server code implemented in a non-bounds-checked language.<br
/><br
/>The server SHOULD use physical controls to isolate a machine capable of directly causing certificate issuance from the public Internet.<br
/><br
/>The server SHOULD attempt to detect fraudulent attempts to issue certificates.<br
/><br
/>The server SHOULD attempt to notify the owner of a site when a certificate was issued, and SHOULD memorialize the issuance publicly using a system such as Certificate Transparency.<br
/><br
/>The client SHOULD be encouraged to use best practices to increase the security of its TLS deployment using the new certificate, such as HSTS and DANE.<br
/><br
/>The server MUST securely generate random session IDs to prevent a malicious client from guessing a valid session ID.&nbsp; The server MUST cause sessions to terminate after a specified period of inactivity or after a fatal error, and prevent any further activity from occurring on a terminated session.&nbsp; After sending a single error message indicating why a session was terminated, the server SHOULD not convey any information to clients about why a particular session is nonexistent or inactive, including whether or not the specified session ever existed.<br
/><br
/>The above list of verification steps may not be exhaustive.&nbsp; Full policy guidelines on these questions will be maintained by the Trustify governance foundation.<br
/><br
/><b>MESSAGE DATA TYPES</b><br
/><br
/>SigningRequest:<br
/><br
/>&nbsp;&nbsp;&nbsp; message SigningRequest {<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; required int64 timestamp = 2;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; required string recipient = 3;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; required string csr = 4;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; required bytes sig = 5;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; optional string clientpuzzle = 6;<br
/>&nbsp;&nbsp;&nbsp; }<br
/><br
/>timestamp is the Unix timestamp when the request was made.&nbsp; (The server SHOULD verify that this is not significantly in the past or future relative to the time that the server received it.)<br
/><br
/>recipient is the URI of the service to which the client intended to submit the request.<br
/><br
/>csr is a PEM-encoded certificate signing request containing the subject public key and all subject names to which the request relates (using "\n" rather than "\r\n" as newline delimiter).<br
/><br
/>sig is an RSA signature over the preceding values using the private key that corresponds to the subject public key.&nbsp; <b><i>TODO</i></b>: describe how the signature is calculated.<br
/><br
/>clientpuzzle is a hashcash string that refers to the hostname of the server.<br
/><br
/><br
/>Failure:<br
/><br
/>&nbsp;&nbsp;&nbsp; message Failure {<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; required FailureReason cause = 1;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; optional string URI = 2;&nbsp; /* for more human-readable information */<br
/>&nbsp;&nbsp;&nbsp; }<br
/><br
/>cause is the FailureReason enumerated type reason why the session or request failed or is being abandoned.<br
/><br
/>URI is an optional reference to a URI where more human-readable information about the failure can be obtained. This can be used to clarify to the human user whether there is an action that could be taken to correct the problem.<br
/><br
/><br
/>Proceed:&nbsp;<br
/><br
/>&nbsp;&nbsp;&nbsp; message Proceed {<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; required int64 timestamp = 1;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; optional int32 polldelay = 2;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br
/><br
/>timestamp is the Unix time when the message was issued.&nbsp; When sent by the server, polldelay is a suggested number of seconds to wait before contacting the server again.<br
/><br
/>Challenge:<br
/><br
/>&nbsp;&nbsp;&nbsp; message Challenge {<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; required ChallengeType type = 1;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; optional string name = 2;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; repeated bytes data = 3;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; optional string URI = 4;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; optional bool succeeded = 5;<br
/>&nbsp;&nbsp;&nbsp; }<br
/><br
/>type is the type of the challenge (as defined in the enumerated type ChallengeType).<br
/><br
/>name is the name or identifier the server assigned to this particular challenge instance.<br
/><br
/>data is an array of arbitrary byte values used by a particular challenge, whose semantics are defined by the corresponding challenge type.<br
/><br
/>URI is a URI associated with the challenge, whose semantics are defined by the corresponding challenge type.<br
/><br
/>succeeded can be used by the client or the server to indicate whether the party mentioning the challenge believes that the challenge has already been satisfied.<br
/><br
/>Success:<br
/><br
/>&nbsp;&nbsp;&nbsp; message Success {<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; required string certificate = 1;<br
/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; optional string chain = 2;<br
/>&nbsp;&nbsp;&nbsp; }<br
/><br
/>certificate is the PEM-encoded certificate that was successfully issued.<br
/><br
/>chain is an optional PEM-encoded certificate chain that chains up from the intermediate certificate authority that issued this certificate to a root certificate authority, in order to allow a verifier to validate this certificate.<br
/><br
/></body>
</html>

Submodule attic/m3crypto deleted from c63d9b1b36

View File

@@ -1,103 +0,0 @@
#!/usr/bin/env python
from pyasn1.type import univ, namedtype
from pyasn1.codec.der import encoder as der_encoder
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_PSS
from Crypto.Hash import SHA, SHA512
from Crypto.Random import random
class rsa_pk(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('n', univ.Integer()),
namedtype.NamedType('e', univ.Integer())
)
def keyid(pem_key_data):
"""Return the hexadecimal keyid for the public key represented by
pem_key_data. (This also works when given a private key; the keyid
returned is then the keyid of the corresponding public key.) Note that
this is only valid for an RSA key, not for any other type of public
key. The caller must verify that pem_key_data represents a valid RSA
public or private key. The keyid calculation is the same one commonly
performed by certificate authorities, as specified in RFC 5280."""
r = RSA.importKey(pem_key_data)
(n, e) = r.publickey().n, r.publickey().e
# Try to forget the other key parameters (in case it was a private key)
del r
pk = rsa_pk()
pk.setComponentByName("n",n)
pk.setComponentByName("e",e)
return SHA.new(der_encoder.encode(pk)).hexdigest()
class POPChallengeResponder(object):
def __init__(self, sought_id, server_nonce):
# XXX TODO: possibly we should enforce constraints on the length
# and structure of the server nonce (e.g. it must be exactly 32
# lowercase hex digits or we won't respond).
self.server_nonce = server_nonce
self.sought_id = sought_id
self.nonce = random.long_to_bytes(random.getrandbits(256)).encode("hex")
# XXX TODO: we should find the private key immediately when the
# responder object is created (if only we knew where to look for
# it!) -- equivalent to calling self.find_priv() with an appropriate
# list of candidate files.
self.privkey = None
def find_priv(self, candidate_paths):
"""Get the private key corresponding to the sought key ID if it is
available in any of the files in candidatepaths."""
for f in candidate_paths:
try:
with open(f) as candidate:
pem_data = candidate.read(65536)
if keyid(pem_data) == self.sought_id:
this_key = RSA.importKey(pem_data)
if this_key.has_private():
# Only private keys are appropriate here, even
# though keyid() is defined for both public and
# private keys!
self.privkey = this_key
del this_key
return
del this_key
except (IOError, ValueError) as e:
# If file can't be read or doesn't contain an RSA key,
# go on to the next file
continue
self.privkey = None
def respond_challenge(self):
if not self.privkey:
# If the matching private key wasn't found, the challenge can't
# be satisfied.
return None
to_sign = "chocolate protocol %s %s" % (self.nonce, self.server_nonce)
# XXX TODO What is an appropriate and safe RSA signature algorithm to
# use for creating signatures? Is the use of PKCS#1 PSS with SHA-512
# safe? Is this implementation free of timing attacks?
sig = PKCS1_PSS.new(self.privkey).sign(SHA512.new(to_sign))
# Try to forget the private key now that it's been used.
self.privkey = None
return (self.nonce, sig)
# Server-side things
# This makes a challenge given a public key. To get the public key from
# a particular certifcate: openssl x509 -in cert.pem -pubkey -noout
def make_challenge(pem_data):
"""Create a proof-of-possession challenge for a particular public key.
(The caller must verify that pem_data is a valid RSA public key.)"""
server_nonce = random.long_to_bytes(random.getrandbits(256)).encode("hex")
return keyid(pem_data), server_nonce
def verify_challenge_response(pubkey, challenge_string, client_nonce, sig):
"""Is sig a valid signature (verified using pubkey) for the combination
of the specified challenge_string and client_nonce?"""
try:
key = RSA.importKey(pubkey)
except:
return False
text = "chocolate protocol %s %s" % (client_nonce, challenge_string)
return PKCS1_PSS.new(key).verify(SHA512.new(text), sig)

View File

@@ -1,26 +0,0 @@
from trustify.protocol import chocolate_pb2
from popchallenge import *
# openssl genrsa 2048 > /tmp/priv.pem
# openssl genrsa 2048 > /tmp/decoy.pem
# openssl rsa -in /tmp/priv.pem -pubout > /tmp/pub.pem
m = chocolate_pb2.chocolatemessage()
r = chocolate_pb2.chocolatemessage()
skid, snonce = make_challenge(open("/tmp/pub.pem"))
m.challenge.add(name="what-is-the-frequency", type=m.ProofOfPossession, data=(skid, snonce))
for challenge in m.challenge:
if challenge.type == m.ProofOfPossession:
pcr = POPChallengeResponder(*challenge.data)
pcr.find_priv(["/tmp/pub.pem", "/tmp/decoy.pem", "/tmp/falkdjaslkdj", "/tmp/priv.pem"])
assert pcr.privkey
cnonce, sig = pcr.respond_challenge()
r.completedchallenge.add(name=challenge.name, type=r.ProofOfPossession, data=(cnonce,sig))
for completedchallenge in r.completedchallenge:
# If there's actually more than one then we'd need to store and use
# multiple different values of snonce.
if completedchallenge.type == r.ProofOfPossession:
print verify_challenge_response(open("/tmp/pub.pem").read(), snonce, *completedchallenge.data)

View File

@@ -1,110 +0,0 @@
message chocolatemessage {
/* Definitions of message data types. */
message SigningRequest {
required int64 timestamp = 2;
required string recipient = 3;
required string csr = 4; /* Use only \n for EOL, not \r\n. */
required bytes sig = 5;
optional string clientpuzzle = 6;
/* server can specify difficulty somehow? */
}
enum FailureReason {
UnsupportedVersion = 0;
AbandonedRequest = 1;
ServerOutage = 2;
ServerGone = 3;
StaleRequest = 4;
BadSignature = 5;
BadCSR = 6;
BadRequest = 7;
NeedClientPuzzle = 8;
CannotIssueThatName = 9;
ExistingCertificate = 10;
UnsafeKey = 11;
ChallengeFailed = 12;
ChallengeTimeout = 13;
}
message Failure {
required FailureReason cause = 1;
optional string URI = 2; /* for more human-readable information */
}
message Proceed {
required int64 timestamp = 1;
optional int32 polldelay = 2;
}
enum ChallengeType {
DomainValidateSNI = 0;
DomainValidateTLSExt = 1;
EmailValidate = 2;
Payment = 3;
ProofOfPossession = 4;
}
message Challenge {
required ChallengeType type = 1;
optional string name = 2;
repeated bytes data = 3; /* Each challenge type must define
a particular number of data fields
to be used, their order, and their
semantics. Changing these details
requires creating a NEW challenge
type. */
optional string URI = 4;
optional bool succeeded = 5;
/* 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;
optional string chain = 2;
}
/* Beginning of protocol fields. */
/* Sent by CLIENT and SERVER to identify the protocol version. */
required int32 chocolateversion = 1;
/* Sent by CLIENT and SERVER to identify the session. Set to
empty string by client at beginning of new session. */
required string session = 2; /* 64 hex digits chosen by the server */
/* Sent by CLIENT at beginning of session */
optional SigningRequest request = 3; /* There should just be one request and the request
must use subject alternate names for every name that
we want to have signed. There could still be multiple
challenges in response -- one or more challenges per
name. */
/* Sent by CLIENT or SERVER to abandon a session */
optional Failure failure = 4; /* Each failure is completely fatal to the protocol, requiring it
to be restarted from the beginning. */
/* Sent by SERVER to request the client to check back later */
optional Proceed proceed = 5;
/* Sent by SERVER to announce challenges or update the client
on whether the server believes the client has successfully
completed them */
repeated Challenge challenge = 6;
/* Sent by CLIENT to announce that the client believes it has
successfully completed a challenge */
repeated Challenge completedchallenge = 7;
/* Sent by SERVER to issue the requested certificate */
optional Success success = 8;
}

View File

@@ -1,208 +0,0 @@
#!/usr/bin/env python2.3
"""Implement Hashcash version 1 protocol in Python
+-------------------------------------------------------+
| Written by David Mertz; released to the Public Domain |
+-------------------------------------------------------+
Double spend database not implemented in this module, but stub
for callbacks is provided in the 'check()' function
The function 'check()' will validate hashcash v1 and v0 tokens, as well as
'generalized hashcash' tokens generically. Future protocol version are
treated as generalized tokens (should a future version be published w/o
this module being correspondingly updated).
A 'generalized hashcash' is implemented in the '_mint()' function, with the
public function 'mint()' providing a wrapper for actual hashcash protocol.
The generalized form simply finds a suffix that creates zero bits in the
hash of the string concatenating 'challenge' and 'suffix' without specifying
any particular fields or delimiters in 'challenge'. E.g., you might get:
>>> from hashcash import mint, _mint
>>> mint('foo', bits=16)
'1:16:040922:foo::+ArSrtKd:164b3'
>>> _mint('foo', bits=16)
'9591'
>>> from sha import sha
>>> sha('foo9591').hexdigest()
'0000de4c9b27cec9b20e2094785c1c58eaf23948'
>>> sha('1:16:040922:foo::+ArSrtKd:164b3').hexdigest()
'0000a9fe0c6db2efcbcab15157735e77c0877f34'
Notice that '_mint()' behaves deterministically, finding the same suffix
every time it is passed the same arguments. 'mint()' incorporates a random
salt in stamps (as per the hashcash v.1 protocol).
"""
import sys
from string import ascii_letters
from math import ceil, floor
from sha import sha
from random import choice
from time import strftime, localtime, time
ERR = sys.stderr # Destination for error messages
DAYS = 60 * 60 * 24 # Seconds in a day
tries = [0] # Count hashes performed for benchmark
def mint(resource, bits=20, now=None, ext='', saltchars=8, stamp_seconds=False):
"""Mint a new hashcash stamp for 'resource' with 'bits' of collision
20 bits of collision is the default.
'ext' lets you add your own extensions to a minted stamp. Specify an
extension as a string of form 'name1=2,3;name2;name3=var1=2,2,val'
FWIW, urllib.urlencode(dct).replace('&',';') comes close to the
hashcash extension format.
'saltchars' specifies the length of the salt used; this version defaults
8 chars, rather than the C version's 16 chars. This still provides about
17 million salts per resource, per timestamp, before birthday paradox
collisions occur. Really paranoid users can use a larger salt though.
'stamp_seconds' lets you add the option time elements to the datestamp.
If you want more than just day, you get all the way down to seconds,
even though the spec also allows hours/minutes without seconds.
"""
ver = "1"
now = now or time()
if stamp_seconds: ts = strftime("%y%m%d%H%M%S", localtime(now))
else: ts = strftime("%y%m%d", localtime(now))
challenge = "%s:"*6 % (ver, bits, ts, resource, ext, _salt(saltchars))
return challenge + _mint(challenge, bits)
def _salt(l):
"Return a random string of length 'l'"
alphabet = ascii_letters + "+/="
return ''.join([choice(alphabet) for _ in [None]*l])
def _mint(challenge, bits):
"""Answer a 'generalized hashcash' challenge'
Hashcash requires stamps of form 'ver:bits:date:res:ext:rand:counter'
This internal function accepts a generalized prefix 'challenge',
and returns only a suffix that produces the requested SHA leading zeros.
NOTE: Number of requested bits is rounded up to the nearest multiple of 4
"""
counter = 0
hex_digits = int(ceil(bits/4.))
zeros = '0'*hex_digits
while 1:
digest = sha(challenge+hex(counter)[2:]).hexdigest()
if digest[:hex_digits] == zeros:
tries[0] = counter
return hex(counter)[2:]
counter += 1
def check(stamp, resource=None, bits=None,
check_expiration=None, ds_callback=None):
"""Check whether a stamp is valid
Optionally, the stamp may be checked for a specific resource, and/or
it may require a minimum bit value, and/or it may be checked for
expiration, and/or it may be checked for double spending.
If 'check_expiration' is specified, it should contain the number of
seconds old a date field may be. Indicating days might be easier in
many cases, e.g.
>>> from hashcash import DAYS
>>> check(stamp, check_expiration=28*DAYS)
NOTE: Every valid (version 1) stamp must meet its claimed bit value
NOTE: Check floor of 4-bit multiples (overly permissive in acceptance)
"""
if stamp.startswith('0:'): # Version 0
try:
date, res, suffix = stamp[2:].split(':')
except ValueError:
ERR.write("Malformed version 0 hashcash stamp!\n")
return False
if resource is not None and resource != res:
return False
if check_expiration is not None:
good_until = strftime("%y%m%d%H%M%S", localtime(time()-check_expiration))
if date < good_until:
return False
if callable(ds_callback) and ds_callback(stamp):
return False
if type(bits) is not int:
return True
hex_digits = int(floor(bits/4))
return sha(stamp).hexdigest().startswith('0'*hex_digits)
elif stamp.startswith('1:'): # Version 1
try:
claim, date, res, ext, rand, counter = stamp[2:].split(':')
except ValueError:
ERR.write("Malformed version 1 hashcash stamp!\n")
return False
if resource is not None and resource != res:
return False
if type(bits) is int and bits > int(claim):
return False
if check_expiration is not None:
good_until = strftime("%y%m%d%H%M%S", localtime(time()-check_expiration))
if date < good_until:
return False
if callable(ds_callback) and ds_callback(stamp):
return False
hex_digits = int(floor(int(claim)/4))
return sha(stamp).hexdigest().startswith('0'*hex_digits)
else: # Unknown ver or generalized hashcash
ERR.write("Unknown hashcash version: Minimal authentication!\n")
if type(bits) is not int:
return True
elif resource is not None and stamp.find(resource) < 0:
return False
else:
hex_digits = int(floor(bits/4))
return sha(stamp).hexdigest().startswith('0'*hex_digits)
def is_doublespent(stamp):
"""Placeholder for double spending callback function
The check() function may accept a 'ds_callback' argument, e.g.
check(stamp, "mertz@gnosis.cx", bits=20, ds_callback=is_doublespent)
This placeholder simply reports stamps as not being double spent.
"""
return False
if __name__=='__main__':
# Import Psyco if available
try:
import psyco
psyco.bind(_mint)
except ImportError:
pass
import optparse
out, err = sys.stdout.write, sys.stderr.write
parser = optparse.OptionParser(version="%prog 0.1",
usage="%prog -c|-m [-b bits] [string|STDIN]")
parser.add_option('-b', '--bits', type='int', dest='bits', default=20,
help="Specify required collision bits" )
parser.add_option('-m', '--mint', help="Mint a new stamp",
action='store_true', dest='mint')
parser.add_option('-c', '--check', help="Check a stamp for validity",
action='store_true', dest='check')
parser.add_option('-s', '--timer', help="Time the operation performed",
action='store_true', dest='timer')
parser.add_option('-n', '--raw', help="Suppress trailing newline",
action='store_true', dest='raw')
(options, args) = parser.parse_args()
start = time()
if options.mint: action = mint
elif options.check: action = check
else:
out("Try: %s --help\n" % sys.argv[0])
sys.exit()
if args: out(str(action(args[0], bits=options.bits)))
else: out(str(action(sys.stdin.read(), bits=options.bits)))
if not options.raw: sys.stdout.write('\n')
if options.timer:
timer = time()-start
err("Completed in %0.4f seconds (%d hashes per second)\n" %
(timer, tries[0]/timer))

Submodule attic/pygeoip deleted from 2bd928e6b3

View File

@@ -1 +0,0 @@
These files only exist to help us test code as we're writing it.

View File

@@ -1,20 +0,0 @@
-----BEGIN NEW CERTIFICATE REQUEST-----
MIIDMTCCAhkCAQAwYDELMAkGA1UEBhMCVVMxCjAIBgNVBAoTAUkxCjAIBgNVBAsT
AUkxFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xCzAJBgNVBAgTAkNBMRQwEgYDVQQD
EwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMvl
meLXw7QBEGNvoFvOaNwGa3aWorfXo24ev0jwC73XKwhGVitdVWBHRMQsnz0ikaeX
Ajr0r4izdokbdFN7tTbGKKANyxEUHI8vx+2wsUpJDhi09MUKdWNy99RoxTbLj/IR
20gPF4/LSt/wbSilyfMztq8sPfNciDI52A7iTSMrvoGwLCl0t/hhgacL6A8Ov4oE
S78KABHG8LeDlW1ShyAV9yva2qth/JQU8bryrXoY8baiuhMrQc1Pl4Ifx44pfbuG
UBN+eGx05p0LZRt+txeLny6ZHtnYVF9HnMmW+IY9h0H9Z3HpSBcw1bLvzJ6QpEwy
+pOyIWaSlWJbTeCA/yUCAwEAAaCBizCBiAYJKoZIhvcNAQkOMXsweTBABgNVHREE
OTA3gg93d3cuZXhhbXBsZS5jb22CEWNyYXp5LmV4YW1wbGUuY29tghF3ZWlyZC5l
eGFtcGxlLmNvbTAMBgNVHRMBAf8EAjAAMA8GA1UdDwEB/wQFAwMHgAAwFgYDVR0l
AQH/BAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQEFBQADggEBAG42o9ZkPIyYGUxW
kAzz4taVx3376uIBgje7h663Yd1HGpXGxL0MPLISIJVkrDTVY2nbCgk2FzJVwzOy
lYWKhWIcGS8JR5In6HRwtF0DMp92uVgRhgY3o7QJEuwH/GYd1BLFOWFWS45q4tXs
dB3RROc7/J+Oo74P907/xJGQiFqTNFNw/Qx7dwQzYjXayENCUU26317psx16T+Rj
TKxP7DQU1qExu4BOrw2go8dpNnKLu01G+jzGLlTQme3Mw5FhsBmDtctOakD94SP3
Yn5fnDJ0EUiwF+aYL0iy2fPAtrzqhJaBraYy50lL7TGuCHT+sd8AZVo8+p37FTwU
cqJLGN0=
-----END NEW CERTIFICATE REQUEST-----

View File

@@ -1,9 +0,0 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0zxdbpiw6k6rdqYNagp7
nFmDxYwKodPDeWlYFp8nAnusgSU/pXnEguiEouHO8MqBPsg1npYQ0pRzyjbWPB6B
oBAaSBo8/1hhDo6nKioVgmfWb02ihmkooj1G4CHFRC/fnkrda8U2fT93ImEJKVET
EINuenfWCLfxkfgkGb4Rh+yhhPBmUcRI6pBHiBPrbcm+U/xmadMcq2OKPzBj0TzK
2ltK+iMdzvlUhu1dLXGMMODOStov/eGBYAPwnRg6Jcyyh0pXA5UA5LZQC6t81DXc
MB0Z8sJWrk+DfdLeYw6IplKofB8qREIkUD2pfcPuu8eT5uW+7GMcHtJ4j8sdoQ4K
9wIDAQAB
-----END PUBLIC KEY-----

View File

@@ -1,11 +0,0 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBizCB9QIBADBMMQswCQYDVQQGEwJHQjESMBAGA1UECBMJQmVya3NoaXJlMRAw
DgYDVQQHEwdOZXdidXJ5MRcwFQYDVQQKEw5NeSBDb21wYW55IEx0ZDCBnzANBgkq
hkiG9w0BAQEFAAOBjQAwgYkCgYEA5fy56mgUe5YqxNxwzLdRricjfVwgc9pRGbYc
sV+uSgRRpGVImD8AD45avTw0wdICGDTAiBAxSQCZfsZfdp42YSuOy/LePj2sTKQk
azOpM9SmOf4E7OPWd94O9Jv809d7EzZh4yu+9tEDVgiDNhqZraHYl3nAwBCOw2lt
CkxUnwUCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4GBANYS454tPh2QD3bS911kS3/F
BqOzbWWuNnh5nxH/ObB2x841h2MujLpOmrjeudON7siRb3VCn/K/rRQ0Q0fbrZ5R
rjWY2bSwYAziRon/JgI7uKnfIjRGktrFwlQptCkFfbr42GmV3mWjaHRqk+udP39m
n8ukO4LSxo1REOW1vdGD
-----END CERTIFICATE REQUEST-----

View File

@@ -1 +0,0 @@
SERVERNAME

View File

@@ -1,217 +0,0 @@
#!/bin/sh
# A fully automated robo-CA does have to have credentials stored somewhere
# that it can use to issue certs on its own initiative! Though ideally
# the actual signing key would be in an HSM, not a text file.
export PASSWORD=dang
# CA - wrapper around ca to make it easier to use ... basically ca requires
# some setup stuff to be done before you can use it and this makes
# things easier between now and when Eric is convinced to fix it :-)
#
# CA -newca ... will setup the right stuff
# CA -newreq ... will generate a certificate request
# CA -sign ... will sign the generated request and output
#
# At the end of that grab newreq.pem and newcert.pem (one has the key
# and the other the certificate) and cat them together and that is what
# you want/need ... I'll make even this a little cleaner later.
#
#
# 12-Jan-96 tjh Added more things ... including CA -signcert which
# converts a certificate to a request and then signs it.
# 10-Jan-96 eay Fixed a few more bugs and added the SSLEAY_CONFIG
# environment variable so this can be driven from
# a script.
# 25-Jul-96 eay Cleaned up filenames some more.
# 11-Jun-96 eay Fixed a few filename missmatches.
# 03-May-96 eay Modified to use 'ssleay cmd' instead of 'cmd'.
# 18-Apr-96 tjh Original hacking
#
# Tim Hudson
# tjh@cryptsoft.com
#
# default openssl.cnf file has setup as per the following
# demoCA ... where everything is stored
cp_pem() {
infile=$1
outfile=$2
bound=$3
flag=0
exec <$infile;
while read line; do
if [ $flag -eq 1 ]; then
echo $line|grep "^-----END.*$bound" 2>/dev/null 1>/dev/null
if [ $? -eq 0 ] ; then
echo $line >>$outfile
break
else
echo $line >>$outfile
fi
fi
echo $line|grep "^-----BEGIN.*$bound" 2>/dev/null 1>/dev/null
if [ $? -eq 0 ]; then
echo $line >$outfile
flag=1
fi
done
}
usage() {
echo "usage: $0 -newcert|-newreq|-newreq-nodes|-newca|-sign|-verify" >&2
}
if [ -z "$OPENSSL" ]; then OPENSSL=openssl; fi
if [ -z "$DAYS" ] ; then DAYS="-days 365" ; fi # 1 year
CADAYS="-days 1095" # 3 years
REQ="$OPENSSL req $SSLEAY_CONFIG"
CA="$OPENSSL ca $SSLEAY_CONFIG"
VERIFY="$OPENSSL verify"
X509="$OPENSSL x509"
PKCS12="openssl pkcs12"
if [ -z "$CATOP" ] ; then CATOP=./demoCA ; fi
CAKEY=./cakey.pem
CAREQ=./careq.pem
CACERT=./cacert.pem
CACFG=./ca.cfg
RET=0
while [ "$1" != "" ] ; do
case $1 in
-\?|-h|-help)
usage
exit 0
;;
-newcert)
# create a certificate
$REQ -new -x509 -keyout newkey.pem -out newcert.pem $DAYS
RET=$?
echo "Certificate is in newcert.pem, private key is in newkey.pem"
;;
-newreq)
# create a certificate request
$REQ -new -keyout newkey.pem -out newreq.pem $DAYS
RET=$?
echo "Request is in newreq.pem, private key is in newkey.pem"
;;
-newreq-nodes)
# create a certificate request
$REQ -new -nodes -keyout newreq.pem -out newreq.pem $DAYS
RET=$?
echo "Request (and private key) is in newreq.pem"
;;
-newca)
# if explicitly asked for or it doesn't exist then setup the directory
# structure that Eric likes to manage things
NEW="1"
if [ "$NEW" -o ! -f ${CATOP}/serial ]; then
# create the directory hierarchy
mkdir -p ${CATOP}
mkdir -p ${CATOP}/certs
mkdir -p ${CATOP}/crl
mkdir -p ${CATOP}/newcerts
mkdir -p ${CATOP}/private
touch ${CATOP}/index.txt
fi
if [ ! -f ${CATOP}/private/$CAKEY ]; then
echo "CA certificate filename (or enter to create)"
read FILE
# ask user for existing CA certificate
if [ "$FILE" ]; then
cp_pem $FILE ${CATOP}/private/$CAKEY PRIVATE
cp_pem $FILE ${CATOP}/$CACERT CERTIFICATE
RET=$?
if [ ! -f "${CATOP}/serial" ]; then
$X509 -in ${CATOP}/$CACERT -noout -next_serial \
-out ${CATOP}/serial
fi
else
echo "Making CA certificate ..."
$REQ -new -keyout ${CATOP}/private/$CAKEY \
-out ${CATOP}/$CAREQ
$CA -create_serial -out ${CATOP}/$CACERT $CADAYS -batch \
-keyfile ${CATOP}/private/$CAKEY -selfsign \
-extensions v3_ca \
-infiles ${CATOP}/$CAREQ
RET=$?
fi
fi
;;
-xsign)
$CA -policy policy_anything -infiles newreq.pem
RET=$?
;;
-pkcs12)
if [ -z "$2" ] ; then
CNAME="My Certificate"
else
CNAME="$2"
fi
$PKCS12 -in newcert.pem -inkey newreq.pem -certfile ${CATOP}/$CACERT \
-out newcert.p12 -export -name "$CNAME"
RET=$?
exit $RET
;;
-sign|-signreq)
$CA -policy policy_anything -out newcert.pem -infiles newreq.pem
RET=$?
cat newcert.pem
echo "Signed certificate is in newcert.pem"
;;
-chocolate)
/bin/echo -e "y\ny\ny\n" | $CA -passin env:PASSWORD -policy policy_anything -out "$3" -infiles "$2"
RET=$?
exit $RET
;;
-complete)
# TODO: for deployed system, add -notext to avoid getting human-readable
# text output.
/bin/echo -e "y\ny\n" | $CA -passin env:PASSWORD -config ${CATOP}/${CACFG} -extfile "$3" -subj "$2" -out "$5" -infiles "$4"
RET=$?
exit $RET
;;
-signCA)
$CA -policy policy_anything -out newcert.pem -extensions v3_ca -infiles newreq.pem
RET=$?
echo "Signed CA certificate is in newcert.pem"
;;
-signcert)
echo "Cert passphrase will be requested twice - bug?"
$X509 -x509toreq -in newreq.pem -signkey newreq.pem -out tmp.pem
$CA -policy policy_anything -out newcert.pem -infiles tmp.pem
RET=$?
cat newcert.pem
echo "Signed certificate is in newcert.pem"
;;
-verify)
shift
if [ -z "$1" ]; then
$VERIFY -CAfile $CATOP/$CACERT newcert.pem
RET=$?
else
for j
do
$VERIFY -CAfile $CATOP/$CACERT $j
if [ $? != 0 ]; then
RET=$?
fi
done
fi
exit $RET
;;
*)
echo "Unknown arg $i" >&2
usage
exit 1
;;
esac
shift
done
exit $RET

View File

@@ -1,40 +0,0 @@
# The name that the server expects to be referred to by.
chocolate_server_name = "ca.theobroma.info"
# Where is a form located through which payments can be submitted
# if required by policy? (note: this address should expect to be
# called followed by a slash and then the 64 hex digit session ID)
payment_uri = "https://ca.theobroma.info/payment.py"
# Where are on-line error messages about failures located?
error_uri = "https://ca.theobroma.info/failures/"
# The shortest length in bits of an acceptable RSA modulus.
min_keysize = 2048
# The number of bits of hashcash that a client must provide with
# a new request.
difficulty = 23
# The number of seconds that the server asks the client to wait, at
# a time, when a request is still being processed.
polldelay = 4
# The maximum number of subject names in a request.
max_names = 20
# The maximum size in bytes of a CSR.
max_csr_size = 20480
# The expiry times of sessions, challenges, and hashcash, in seconds.
maximum_session_age = 100
maximum_challenge_age = 600
hashcash_expiry = 60*60
# Extra names that the CA refuses to issue for, apart from those in
# the blacklist table in the database.
extra_name_blacklist = ["eff.org", "www.eff.org"]
# Name of file containing cert chain
cert_chain_file = "demoCA/cacert.pem"
debug = True

View File

@@ -1,254 +0,0 @@
#!/usr/bin/env python
# use OpenSSL to provide CSR-related operations
import site, os
assert os.path.exists("../m3/lib/python"), "\nPlease install m3crypto into ../m3/lib/python by running\nmkdir -p ../m3/lib/python; PYTHONPATH=../m3/lib/python python setup.py install --home=../m3\nfrom inside the m3crypto directory."
site.addsitedir("../m3/lib/python")
import subprocess, re
from tempfile import NamedTemporaryFile as temp
import M2Crypto
from distutils.version import LooseVersion
assert LooseVersion(M2Crypto.version) >= LooseVersion("0.22")
import hashlib
import blacklists
# we can use temp() to get tempfiles to pass to OpenSSL subprocesses.
from CONFIG import min_keysize
forbidden_moduli = blacklists.forbidden_moduli()
forbidden_names = blacklists.forbidden_names()
def parse(csr):
"""
Is this CSR syntactically valid? (TODO: remove)
@type csr: str
@param csr: PEM-encoded string of the CSR.
@return: True if M2Crypto can parse the csr,
False if there is an error parsing it.
"""
try:
csr = str(csr)
req = M2Crypto.X509.load_request_string(csr)
return True
except Exception, e:
return False
def modulusbits(key):
"""How many bits are in the modulus of this key?"""
key = str(key)
bio = M2Crypto.BIO.MemoryBuffer(key)
pubkey = M2Crypto.RSA.load_pub_key_bio(bio)
return len(pubkey)
def goodkey(key):
"""Does this public key comply with our CA policy?"""
key = str(key)
bits = modulusbits(key)
if bits and bits >= min_keysize and not blacklisted(key):
return True
else:
return False
def blacklisted(key):
"""Is this key blacklisted?"""
# There is also a modulus function that uses M2Crypto.m2.rsa_get_n
# instead of EVP.PKey, but it seems to erroneously prepend the exponent
# to the modulus or something.
bio = M2Crypto.BIO.MemoryBuffer(key)
pubkey = M2Crypto.RSA.load_pub_key_bio(bio)
pkey = M2Crypto.EVP.PKey()
pkey.assign_rsa(pubkey)
modulus = pkey.get_modulus()
# The modulus is now in hexadecimal, all uppercase.
modulus = hashlib.sha1("Modulus=%s\n" % modulus).hexdigest()[20:]
# This is the format in which moduli are represented by the
# openssl-blacklist package (using a hash of the literal output
# of the openssl -rsa -modulus -pubin -noout command, including
# newline).
return modulus in forbidden_moduli
def csr_goodkey(csr):
"""Does this CSR's embedded public key comply with our CA policy?"""
csr = str(csr)
if not parse(csr): return False
key = pubkey(csr)
return goodkey(key)
def pubkey(csr):
"""
Get the public key from this Certificate Signing Request.
@type csr: string
@param csr: PEM-encoded string of the CSR.
@return: a string of the PEM-encoded public key
"""
csr = str(csr)
req = M2Crypto.X509.load_request_string(csr)
return req.get_pubkey().get_rsa().as_pem(None)
def subject(csr):
"""
Get the X.509 subject from this CSR.
@type csr: string
@param csr: PEM-encoded string of the CSR.
@return: a string of the subject
"""
csr = str(csr)
req = M2Crypto.X509.load_request_string(csr)
return req.get_subject().as_text()
def cn(csr):
"""
Get the common name from this CSR. Requires there be exactly one CN
(of type ASN1_string)
@type csr: str
@param csr: PEM-encoded string of the CSR.
@return: string of the first
"""
csr = str(csr)
req = M2Crypto.X509.load_request_string(csr)
# Get an array of CNs
cns = req.get_subject().get_entries_by_nid(M2Crypto.X509.X509_Name.nid['CN'])
# If it's not 1, we've got problems (throw error?)
if len(cns) != 1:
return None
return cns[0].get_data().as_text()
def subject_names(csr):
"""
Get the cn and subjectAltNames from this CSR
(removing duplicates but retaining original order).
@type csr: str
@param csr: PEM-encoded string of the CSR
@return: array of strings of subject (CN) and subject
alternative names (x509 extension)
"""
csr = str(csr)
names = []
names.append(cn(csr).lower())
req = M2Crypto.X509.load_request_string(csr)
for ext in req.get_extensions(): # requires M3Crypto modification
if ext.get_name() == 'subjectAltName': # TODO: can we trust this?
# 'DNS:example.com, DNS:www.example.com'
sans = ext.get_value().split(',')
for san in sans:
san = san.strip() # remove leading space
if san.startswith('DNS:'):
new_name = san[len('DNS:'):].lower()
if new_name not in names:
names.append(new_name)
# Don't exit loop - support multiple SAN extensions??
return names
def can_sign(name):
"""Does this CA's policy forbid signing this name via Chocolate DV?"""
# We could have a regular expression match here, like
# ([a-z0-9]+\.)+[a-z0-9]+
# and there is also a list of TLDs to check against to confirm that
# the name is actually a FQDN.
name = str(name)
if "." not in name: return False
# Names that are forbidden by policy due to a blacklist.
return name not in forbidden_names
def verify(key, data, signature):
"""
Given a public key, some data, and its signature,
verify the signature.
@type key: str
@param key: PEM-encoded string of the public key.
@type data: str
@param data: The data (before being hashed; we will use sha256 here)
@type signature: str
@param signature: binary string of the signature
@return: True if the signature checks out, False otherwise.
"""
key = str(key)
data = str(data)
signature = str(signature)
bio = M2Crypto.BIO.MemoryBuffer(key)
pubkey = M2Crypto.RSA.load_pub_key_bio(bio)
try:
res = pubkey.verify(hashlib.sha256(data).digest(), signature, 'sha256')
except M2Crypto.RSA.RSAError:
return False
return (res == 1)
def encrypt(key, data):
"""
Encrypt this data with this public key.
@type key: str
@param key: PEM-encoded string of the public key
@type data: str
@param data: The data to be encrypted.
@return: binary string of the encrypted value, using PKCS1_OAEP_PADDING
"""
key = str(key)
data = str(data)
bio = M2Crypto.BIO.MemoryBuffer(key)
pubkey = M2Crypto.RSA.load_pub_key_bio(bio)
return pubkey.public_encrypt(data, M2Crypto.RSA.pkcs1_oaep_padding)
def issue(csr, subjects):
"""Issue a certificate requested by CSR, specifying the subject names
indicated in subjects, and return the certificate. Calls to this
function should be guarded with a lock to ensure that the calls never
overlap."""
if not subjects:
return None
csr = str(csr)
subjects = [str(s) for s in subjects]
for s in subjects:
if ":" in s or "," in s or " " in s or "\n" in s or "\r" in s:
# We should already have validated the names to be issued a
# long time ago, but this is an extra sanity check to make
# sure that the cert issuing process can't be corrupted by
# attempting to issue certs for names with special characters.
return None
cert = None
# We need three temporary files: for the CSR, for the extension config
# file, and for the resulting certificate.
with temp() as csr_tmp, temp() as ext_tmp, temp() as cert_tmp:
csr_tmp.write(csr)
csr_tmp.flush()
dn = "/CN=%s" % subjects[0]
ext_tmp.write("""
basicConstraints=CA:FALSE
keyUsage=digitalSignature, keyEncipherment, keyAgreement
extendedKeyUsage=serverAuth
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
nsComment = "Chocolatey"
""")
san_line = "subjectAltName="
san_line += ",".join("DNS:%s" % n for n in subjects) + "\n"
ext_tmp.write(san_line)
ext_tmp.flush()
ret = subprocess.Popen(["./CA.sh", "-complete", dn, ext_tmp.name, csr_tmp.name, cert_tmp.name],shell=False,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE).wait()
if ret == 0:
cert = cert_tmp.read()
return cert

View File

@@ -1,8 +0,0 @@
# deploy: chocolate_protocol_pb2.py chocolate.py CSR.py daemon.py CA.sh
# scp chocolate_protocol.proto chocolate.py CSR.py daemon.py CA.sh ${CHOCOLATESERVER}:
# ssh ${CHOCOLATESERVER} protoc chocolate_protocol.proto --python_out=.
# rsync -av --delete sni_challenge demoCA ${CHOCOLATESERVER}:
# ssh ${CHOCOLATESERVER} make -C sni_challenge clean all
clean:
rm -f *.pyc

View File

@@ -1,30 +0,0 @@
In this directory is a reference CA implementation of the Chocolate protocol,
DV and signing mechanism.
Instead of using "make deploy", we're currently using git pull to deploy this.
This requires restarting lighttpd on the server and ensuring that Redis and
a copy of daemon.py are running there. If the .proto definition has
changed, it also needs to be recompiled on both the server and the client.
chocolate.py - server-side, requires web.py (python-webpy),
PyCrypto (python-crypto) 2.3 (not 2.1!!), redis, python-redis,
python-protobuf, "M3Crypto" (from our own tree) (hence also
build-essential, python-dev, and swig)
probably wants to run under a web server like lighttpd with fastcgi
daemons/{makechallenge,testchallenge,issue,logging}-daemon.py -
daemons to handle back-end implementation of protocol state transitions
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 verify the
server.
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?)
requires python-socksipy, tor

View File

@@ -1,65 +0,0 @@
Database structure and procedures for the CA server
===================================================
This is information about how sessions and their progress are
represented inside the Redis database and what daemons should do
to advance the progress of a session.
Note that all values in Redis are normally stored as strings,
so references to "int" mean that the value in the database should
be INTERPRETED as an integer, not that it is natively stored as
one.
Sessions:
---------
hash: sessionid, "created" → int
sessionid, "live" → "True", "False"
sessionid, "csr" → str
sessionid, "state" → str
sessionid, "challenges → int
sessionid, "cert" → str
list: session:names → str
Challenges:
-----------
hash: sessionid:n, "challtime" → int
sessionid:n, "type" → int
sessionid:n, "name" → str
sessionid:n, "satisfied" → "True","False"
sessionid:n, "failed" → "True","False"
sessionid:n, "data" → str # This is problematic.
Queues:
-------
list: active-requests
list: pending-makechallenge
list: pending-testchallenge
list: pending-issue
list: pending-done
Daemons should FIRST pop sessions from a queue (and check whether the
thing that was pending has already been done?); then do the thing that
was pending; then change the session's state to the next state; then
push the session ID onto the next queue.
If the server crashes, then upon reinitalization the server can go
through everything in active-requests and push it onto the appropriate
pending-whatever queue for its state if it isn't already in that queue.
How do sessions get removed from the pending-done queue? (The server
can tell when it's told a client about a certificate issuance, but not
whether the client has received the good news.) Do we need to have
clients send a "thanks" message to let the server know that they've
received the cert? This could still lead to cases where the client has
received a cert but the CA never finds out about it because the client
crashes or goes offline afterward. However, without the acknowledgement
we could have the opposite problem (the client hasn't gotten a cert, but
the CA thinks it has).

View File

@@ -1,37 +0,0 @@
# The queue mechanism with pending-* is supposed to control
# concurrency issues properly, but this needs verification
# to ensure that there are no possible race conditions.
# Generally, the server process (as distinct from the daemon)
# is not supposed to change sessions at all once they have
# been added to a queue, except for marking them no longer
# live if the server realizes that something bad has happened
# to them. There may be some exceptions, and they should all
# be analyzed for possible races.
# TODO: check sessions' internal evidence for consistency
# with their queue membership (in case of crashes or bugs).
# In particular, check that a session in pending-makechallenge
# does not actually contain any challenges and that a
# session in pending-issue does not actually contain an
# issued cert.
# TODO: write queue rebuilding script that uses sessions'
# internal state to decide which queue they go in (to
# run when starting daemon, in case there was a crash
# that caused a session not to be in any pending queue
# because the daemon was actively working on it during
# the crash); consider marking sessions "dirty" when
# beginning to actually modify their contents in order
# to allow dirty sessions to be deleted after a crash instead
# of placing them back on a queue. Or, we could just
# decide that a crash invalidates each and every pending
# request, period, while still allowing clients to look
# up successfully issued certs.
# NOTE: The daemon enforces its own timeouts, which are
# defined in the ancient() function. These timeouts apply
# to any session that has been placed in a queue and can
# be completely independent of the session timeout policy
# in the server. Being marked as dead at any time by either
# the server or the daemon (due to timeout or error) causes
# a session to be treated as dead by both.

View File

@@ -1,24 +0,0 @@
#!/usr/bin/env python
# This imports the factorable moduli blacklist file into the
# Redis set "factorable_moduli". Specify one or more files on the
# command line to import them.
# E.g.,
# python import-openssl-blacklist.py factorable_moduli.txt
# will import everything. This assumes that the input moduli are
# already hexadecimal. This script converts the moduli into the Debian
# blacklist format before inserting them into Redis.
import sys, redis, hashlib
r = redis.Redis()
for f in sys.argv[1:]:
for line in list(open(f)):
m = line.upper().strip()
m2 = m.lstrip("0") # version without leading zeroes
h1 = hashlib.sha1("Modulus=%s\n" % m).hexdigest()[20:]
h2 = hashlib.sha1("Modulus=%s\n" % m2).hexdigest()[20:]
r.sadd("factorable_moduli", h1)
r.sadd("factorable_moduli", h2)

View File

@@ -1,27 +0,0 @@
#!/usr/bin/env python
# This imports a Debian OpenSSL modulus blacklist file into the
# Redis set "debian_moduli". Specify one or more files on the
# command line to import them. Importing will require somewhere
# around a minute per file.
# E.g.,
# python import-openssl-blacklist.py /usr/share/openssl-blacklist/blacklist.*
# will import everything (including 1024 and 512 bit moduli, which might be
# rejected for other reasons).
# It would probably be a lot faster to make this use
# http://redis.io/topics/mass-insert
# instead of the Python redis library, or, indeed, to simply use
# grep -hv '#' /usr/share/openssl-blacklist/blacklist.RSA-* | sed 's/^/sadd debian_moduli /' | redis-cli --pipe
# but this requires redis-cli 2.4, and our test systems all have only
# redis-cli 2.2.12.
import sys, redis
r = redis.Redis()
for f in sys.argv[1:]:
for line in open(f):
if "#" not in line and len(line.rstrip()) == 20:
r.sadd("debian_moduli", line.rstrip())

View File

@@ -1,25 +0,0 @@
#!/usr/bin/env python
import redis
r = redis.Redis()
# You can test strings for membership in instances of these classes
# in order to search modulus and name blacklists kept in Redis.
# Moduli should be stored in Redis in openssl-vulnkey(1) format,
# which is the rightmost 20 characters of the SHA1 hash
# of "Modulus=%s\n" % modulus.upper().
#
# If these sets don't exist, sismember returns False (not an exception).
# Redis set membership testing is very fast. These classes are just
# making particular Redis sets look like Python sets or dictionaries for
# membership testing purposes.
class forbidden_moduli(object):
def __contains__(self, modulus):
return r.sismember("debian_moduli", modulus) or r.sismember("factorable_moduli", modulus)
class forbidden_names(object):
def __contains__(self, name):
return r.sismember("forbidden_names", name)

View File

@@ -1,466 +0,0 @@
#!/usr/bin/env python
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
from trustify.protocol.chocolate_pb2 import chocolatemessage
from google.protobuf.message import DecodeError
from CONFIG import chocolate_server_name, min_keysize, difficulty, polldelay
from CONFIG import max_names, max_csr_size, maximum_session_age
from CONFIG import maximum_challenge_age, hashcash_expiry, extra_name_blacklist
from CONFIG import cert_chain_file, debug, payment_uri, error_uri
poll_interval = 10
try:
chocolate_server_name = open("SERVERNAME").read().rstrip()
except IOError:
raise IOError, "Please create a SERVERNAME file containing the server name."
urls = (
'.*', 'session'
)
def random():
"""Return 64 hex digits representing a new 32-byte random number."""
return binascii.hexlify(Random.get_random_bytes(32))
def safe(what, s):
"""Is string s within the allowed-character policy for this field?"""
if not isinstance(s, basestring):
return False
if len(s) == 0:
# No validated string should be empty.
return False
base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
csr_ok = base64 + " =-"
if what == "recipient" or what == "hostname":
# This rejects domain names which don't contain ".". Although there
# are some of these which are valid Internet FQDNs, none of them
# should be subjects or recipients of Chocolate signing requests.
return re.match("^[A-Za-z0-9][A-Za-z0-9-]*(\.[A-Za-z0-9][A-Za-z0-9-]*)+$", s) is not None
elif what == "csr":
return all(all(c in csr_ok for c in line) for line in s.split("\n"))
# Note that this implies CSRs must have LF for end-of-line, not CRLF
elif what == "session":
return len(s) == 64 and all(c in "0123456789abcdef" for c in s)
else:
return False
def short(thing):
"""Return the first 8 bytes of a session ID, or, for a
challenge ID, the challenge ID with the session ID truncated."""
tmp = thing.partition(":")
return tmp[0][:8] + ".." + tmp[1] + tmp[2]
sessions = redis.Redis()
class session(object):
def __init__(self):
self.id = None
def exists(self):
return self.id in sessions
def live(self):
return self.id in sessions and sessions.hget(self.id, "live") == "True"
def state(self):
# Should be:
# * None for a session where the signing request has not
# yet been received;
# * "makechallenge" where the CA is still coming up with challenges,
# * "testchallenge" where the challenges have been issued,
# * "payment" where the recipient must pay for the certificate,
# * "issue" where the CA is in the process of issuing the cert,
# * "done" where the cert has been issued.
#
# Note that this is independent of "live", which specifies whether
# further actions involving this session are permitted. When
# sessions die, they currently keep their last state, but the
# client can't cause their state to advance further. For example,
# if a session times out while waiting for the client to complete
# a challenge, we have state="testchallenge", but live="False".
return sessions.hget(self.id, "state")
def create(self, timestamp=None):
if timestamp is None: timestamp = int(time.time())
if not self.exists():
sessions.hset(self.id, "created", timestamp)
sessions.hset(self.id, "lastpoll", 0)
sessions.hset(self.id, "times-tested", 0)
sessions.hset(self.id, "live", True)
sessions.lpush("active-requests", self.id)
else:
raise KeyError
def kill(self):
# It is now possible to get here via die() even if there is no session
# ID, because we can die() on the initial request before a session ID
# has been allocated!
if self.id:
sessions.hset(self.id, "live", False)
sessions.lrem("active-requests", self.id)
def age(self):
return int(time.time()) - int(sessions.hget(self.id, "created"))
def poll_age(self):
return float(time.time()) - float(sessions.hget(self.id, "lastpoll"))
def request_test(self):
"""Ask a daemon to test challenges."""
# There is a race condition between testing for membership and
# adding it, but it's quite difficult to "exploit" and the result
# of triggering it is just that the same session will be scheduled
# for testing twice. We use locking in the daemon to exclude the
# possibility of two daemon processes testing the same session at
# once, and check the session's state before beginning to test it.
if self.id not in sessions.lrange("pending-testchallenge", 0, -1):
sessions.lpush("pending-testchallenge", self.id)
def request_made(self):
"""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")
def add_request(self, csr, names):
sessions.hset(self.id, "csr", csr)
for name in names: sessions.rpush(self.id + ":names", name)
sessions.hset(self.id, "client-addr", web.ctx.ip)
sessions.hset(self.id, "state", "makechallenge")
sessions.lpush("pending-makechallenge", self.id)
return True
def challenges(self):
n = int(sessions.hget(self.id, "challenges"))
for i in xrange(n):
yield sessions.hgetall("%s:%d" % (self.id, i))
def send_cert(self, m, r):
"""Initialize response to return issued cert to client."""
if self.cert():
r.success.certificate = self.cert()
if cert_chain_file:
try:
r.success.chain = open(cert_chain_file).read()
except IOError:
# Whoops!
pass
else:
self.die(r, r.BadRequest, uri="%sinternalerror" % error_uri)
return
def check_hashcash(self, h, n):
"""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, 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)
def handlesession(self, m, r):
if r.failure.IsInitialized(): return
# Note that m.challenge and m.completedchallenge present
# as lists, which are True if they are nonempty. By
# contrast, m.proceed, m.success, m.request, and m.failure
# are always True but have an .IsInitialized() property
# indicating whether they are actually present in m as
# messages from the client.
#
# Check for some ways in which the message from the client
# can be inappropriate.
if m.challenge or m.proceed.IsInitialized() or m.success.IsInitialized():
self.die(r, r.BadRequest, uri="%sinvalidfromclient" % error_uri)
return
distinct_messages = 0
if m.request.IsInitialized(): distinct_messages += 1
if m.failure.IsInitialized(): distinct_messages += 1
if m.completedchallenge: distinct_messages += 1
if distinct_messages > 1:
self.die(r, r.BadRequest, uri="%smixedmessages" % error_uri)
return
# The rule that a new session must contain a request is enforced
# by handlenewsession. The rule that an existing session must
# not contain a request is enforced by handleexistingsession.
# TODO: check that there are no bad cases that slip through.
if m.session == "":
# New session
r.session = random()
self.id = r.session
if not self.exists():
self.create()
self.handlenewsession(m, r)
else:
raise ValueError, "new random session already existed!"
elif m.session and not r.failure.IsInitialized():
r.session = ""
if not safe("session", m.session):
# Note that self.id is still uninitialized here.
self.die(r, r.BadRequest, uri="%sillegalsession" % error_uri)
return
self.id = m.session
r.session = m.session
if not (self.exists() and self.live()):
# Don't need to, or can't, kill nonexistent/already dead session
r.failure.cause = r.StaleRequest
elif self.age() > maximum_session_age:
# TODO: Sessions in state "done" should probably not be killed by timeout
# because they have already resulted in issuance of a cert and no further
# issuance can occur. At least, their timeout should probably be extended
# to 48 hours or something. Currently, a session can die by timeout in
# any state. In general, the allowed age of a session that's further
# along in the process should be longer. This is particular true when
# we're testing challenges because the amount of time required for this
# may not be under the client's control at all.
self.die(r, r.StaleRequest)
else:
self.handleexistingsession(m, r)
def handlenewsession(self, m, r):
if r.failure.IsInitialized(): return
if not m.request.IsInitialized():
# It is mandatory to make a signing request at the outset of a session.
self.die(r, r.BadRequest, uri="%smissingrequest" % error_uri)
return
timestamp = m.request.timestamp
recipient = m.request.recipient
csr = m.request.csr
sig = m.request.sig
# Log full session ID so it's available
self.log(self.id)
self.log("new session from %s" % web.ctx.ip)
# Check whether we are the intended recipient of the request. Doing this
# before the hashcash check is more work for the server but gives a more
# helpful error message (because the hashcash will be wrong automatically
# if it's addressed to a different server!).
if recipient != chocolate_server_name:
self.die(r, r.BadRequest, uri="%srecipient" % error_uri)
return
# Check hashcash before doing any crypto or database access.
names = CSR.subject_names(csr)
if not m.request.clientpuzzle or not self.check_hashcash(m.request.clientpuzzle, len(names)):
self.die(r, r.NeedClientPuzzle, uri="%shashcash" % error_uri)
return
if self.request_made():
# Can't make new signing requests if there have already been requests in
# this session. (All signing requests should occur together at the
# beginning.)
self.die(r, r.BadRequest, uri="%spriorrequest" % error_uri)
return
# Process the request.
if not all([safe("recipient", recipient), safe("csr", csr)]):
self.die(r, r.BadRequest, uri="%sillegalcharacter" % error_uri)
return
if len(csr) > max_csr_size:
self.die(r, r.BadCSR, uri="%slongcsr" % error_uri)
return
if not CSR.parse(csr):
self.die(r, r.BadCSR)
return
digest_data = "(%d) (%s) (%s)" % (timestamp, recipient, csr)
if CSR.verify(CSR.pubkey(csr), digest_data, sig) == False:
self.die(r, r.BadSignature)
return
if not CSR.csr_goodkey(csr):
self.die(r, r.UnsafeKey)
return
if len(names) == 0:
self.die(r, r.BadCSR)
return
if len(names) > max_names:
self.die(r, r.BadCSR, uri="%stoomanynames" % error_uri)
return
for san in names: # includes CN as well as SANs
if not safe("hostname", san) or not CSR.can_sign(san) or san in extra_name_blacklist:
# TODO: Is there a problem including client-supplied data in the URL?
self.die(r, r.CannotIssueThatName, uri="%sname?%s" % (error_uri,san))
return
try:
# Check whether the SSL Observatory has seen a valid cert for this name.
# XXX: This has been disabled because this API is unavailable
# or unreliable.
if False and urllib2.urlopen("https://observatory.eff.org/check_name?domain_name=%s" % san).read().strip() != "False":
self.die(r, r.CannotIssueThatName, uri="%sobservatory?%s" % (error_uri,san))
return
wildcard_variant = "*." + san.partition(".")[2]
if False and urllib2.urlopen("https://observatory.eff.org/check_name?domain_name=%s" % wildcard_variant).read().strip() != "False":
self.die(r, r.CannotIssueThatName, uri="%sobservatory?%s" % (error_uri,san))
return
except urllib2.HTTPError:
# Currently, don't consider it fatal if the Observatory blacklist
# service is inaccessible.
pass
# Phew!
self.add_request(csr, names)
# This version is relying on an external daemon process to create
# the challenges. If we want to create them ourselves, we have to
# do what the daemon does, and then return the challenges instead
# of returning proceed.
r.proceed.timestamp = int(time.time())
r.proceed.polldelay = polldelay
def handleexistingsession(self, m, r):
self.log("received message from %s" % web.ctx.ip)
if m.request.IsInitialized():
self.die(r, r.BadRequest, uri="%srequestinexistingsession" % error_uri)
return
# The caller has verified that this session exists and is live.
# If we have no state, something is crazy (maybe a race from two
# instances of the client?).
state = self.state()
if state is None:
self.die(r, r.BadRequest, uri="%suninitializedsession" % error_uri)
return
# If we're in makechallenge or issue, tell the client to come back later.
if state == "makechallenge" or state == "issue":
r.proceed.timestamp = int(time.time())
r.proceed.polldelay = polldelay
return
# If we're in testchallenge, tell the client about the challenges and their
# current status.
if state == "testchallenge":
# If the client claims to have completed some challenges, try to test
# them, if the client hasn't asked us to do so too recently.
if m.completedchallenge:
try:
with redis_lock(sessions, "lock-" + self.id, one_shot=True):
if self.poll_age() < poll_interval:
# Too recent!
pass
else:
sessions.hset(self.id, "lastpoll", time.time())
self.request_test()
except KeyError:
pass
self.send_challenges(m, r)
return
if state == "payment":
# If policy has decreed that we need to collect a payment before issuing
# this cert, tell the client about where to go to submit the payment.
# This is presented to the client as a "challenge", although it is
# currently not represented that way in the session database.
# TODO: consider session expiry and frequency limits when in this state
self.send_payment_request(m, r)
return
# If we're in done, tell the client about the successfully issued cert.
if state == "done":
self.send_cert(m, r)
return
# Unknown session status.
self.die(r, r.BadRequest, uri="%sinternalerror" % error_uri)
return
# TODO: Process challenge-related messages from the client.
def log(self, msg):
sessions.publish("logs", "%s: %s" % (short(self.id), msg))
if debug: print "%s: %s" % (self.id, msg)
def die(self, r, reason, uri=None):
self.kill()
r.failure.cause = reason
if uri: r.failure.URI = uri
self.log("from: %s" % web.ctx.ip)
self.log("died: %s" % str(r.failure).split(":")[1].strip())
if uri: self.log("error URI: %s" % uri)
def handleclientfailure(self, m, r):
if r.failure.IsInitialized(): return
if m.failure.IsInitialized():
# Received failure message from client!
self.die(r, r.AbandonedRequest)
def send_challenges(self, m, r):
if r.failure.IsInitialized(): return
# TODO: This needs a more sophisticated notion of success/failure.
for c in self.challenges():
# Currently, we can only handle challenge type 0 (dvsni)
# TODO: unify names "succeeded" vs. "satisfied"?
if int(c["type"]) != 0:
self.die(r, r.BadRequest, uri="%sinternalerror" % error_uri)
return
chall = r.challenge.add()
chall.type = int(c["type"])
chall.name = c["name"]
chall.succeeded = (c["satisfied"] == "True") # TODO: this contradicts comment in protocol about meaning of "succeeded"
# Calculate y
dvsni_r = c["dvsni:r"]
bio = M2Crypto.BIO.MemoryBuffer(self.pubkey())
pubkey = M2Crypto.RSA.load_pub_key_bio(bio)
y = pubkey.public_encrypt(dvsni_r, M2Crypto.RSA.pkcs1_oaep_padding)
# In dvsni, we send nonce, y, ext
chall.data.append(c["dvsni:nonce"])
chall.data.append(y)
chall.data.append(c["dvsni:ext"])
def send_payment_request(self, m, r):
if r.failure.IsInitialized(): return
# This does NOT get the payment challenge out of the session database.
# Instead, it synthesizes a single (fixed) payment challenge for this
# session, with the challenge name "payment". This is less general
# than it might be because, for example, it means only one payment can
# be required per session and payment challenges cannot be sent
# together with dvsni challenges inside a single message. Here, we
# assume that the client would prefer to hear about payment challenges
# only after dvsni validation is complete, for example so that the
# user does not try to pay for a request that will later be rejected
# for other reasons.
chall = r.challenge.add()
chall.type = r.Payment
chall.name = "payment"
chall.succeeded = False
# In payment, we send address of form to complete this payment
abbreviation = sessions.hget(self.id, "shorturl")
chall.data.append(str("%s/%s" % (payment_uri, abbreviation)))
def POST(self):
web.header("Content-type", "application/x-protobuf+chocolate")
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
self.handleclientfailure(m, r)
self.handlesession(m, r)
# TODO: perhaps some code belongs here to enforce rules about which
# combinations of protocol messages can occur together in the reply.
# I think the rules are: server must send exactly one of failure,
# proceed, challenge, or success; server may not send request or
# completedchallenge [although we know it never attempts to].
# If, for some reason, the server is trying to send more than one
# of these messages, or no message at all, that's an error and the
# response should be cleared and we should self.die(r, r.BadRequest)
# or similar.
# Send reply
return r.SerializeToString()
def GET(self):
web.header("Content-type", "text/html")
return "Hello, world! This server only accepts POST requests.\r\n"
if __name__ == "__main__":
app = web.application(urls, globals())
app.run()
# vim: set tabstop=4 shiftwidth=4 expandtab

View File

@@ -1,10 +0,0 @@
#!/usr/bin/env python
print "WARNING: Redis database will be cleared!"
raw_input("Press Enter to continue. ")
import redis
r = redis.Redis()
for i in xrange(len(r.keys())):
r.delete(r.randomkey())

View File

@@ -1,35 +0,0 @@
#!/usr/bin/env python
# functions common to the various kinds of daemon
import time, binascii
from Crypto import Random
import redis
log_redis = redis.Redis()
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 8 bytes of a session ID, or, for a
challenge ID, the challenge ID with the session ID truncated."""
tmp = session.partition(":")
return tmp[0][:8] + ".." + tmp[1] + tmp[2]
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)
def log(msg, session = None):
if session:
log_redis.publish("logs", "%s: %s" % (short(session), msg))
else:
log_redis.publish("logs", "%s" % session)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@
newcerts

View File

@@ -1,6 +0,0 @@
This is a toy CA for us to play with. The password is "dang". The way to use
it is:
cd ..
cp $YOUR_CSR_FILE newreq.pem
./CA.sh -sign

View File

@@ -1,38 +0,0 @@
[ ca ]
default_ca = CA_default # The default ca section
[ CA_default ]
policy = policy_blank
email_in_dn = no
name_opt = ca_default
cert_opt = ca_default
copy_extensions = none
dir = ./demoCA # Where everything is kept
certs = $dir/certs # Where the issued certs are kept
crl_dir = $dir/crl # Where the issued crl are kept
database = $dir/index.txt # database index file.
unique_subject = no # Set to 'no' to allow creation of
# several ctificates with same subject.
new_certs_dir = $dir/newcerts # default place for new certs.
certificate = $dir/cacert.pem # The CA certificate
serial = $dir/serial # The current serial number
crlnumber = $dir/crlnumber # the current crl number
# must be commented out to leave a V1 CRL
crl = $dir/crl.pem # The current CRL
private_key = $dir/private/cakey.pem# The private key
RANDFILE = $dir/private/.rand # private random number file
# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
# so this is commented out by default to leave a V1 CRL.
# crlnumber must also be commented out to leave a V1 CRL.
# crl_extensions = crl_ext
default_days = 365 # how long to certify for
default_crl_days= 30 # how long before next CRL
default_md = default # use public key default MD
preserve = no # keep passed DN ordering
[ policy_blank ]
commonName = supplied

View File

@@ -1,81 +0,0 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
93:a5:80:a1:e0:d9:b5:c2
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=US, ST=CA, O=Internet Widgits Pty Ltd, CN=notreally.eff.org/emailAddress=notreally@eff.org
Validity
Not Before: Jul 7 20:07:59 2012 GMT
Not After : Jul 7 20:07:59 2015 GMT
Subject: C=US, ST=CA, O=Internet Widgits Pty Ltd, CN=notreally.eff.org/emailAddress=notreally@eff.org
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:a8:17:94:8f:63:55:21:48:73:bf:df:58:65:08:
f8:23:eb:0a:6d:53:2e:f4:92:93:05:ba:0b:c5:f8:
c6:ce:dd:cc:64:53:2e:90:66:ae:84:63:64:d3:af:
3b:e1:f0:1c:57:34:c2:cd:f6:ec:ea:cd:07:b3:2e:
5c:32:13:62:aa:06:ac:1d:41:ee:26:7c:6f:c1:d7:
ab:3e:cf:8f:49:89:0b:bd:89:78:a7:2d:c6:74:91:
6f:cb:70:0a:79:ea:b3:bd:2a:58:e7:44:07:93:19:
a8:e2:06:24:be:3c:5d:6d:25:1a:85:f8:96:3e:f1:
b3:08:8c:86:c5:0f:01:0e:0f:34:06:d4:94:73:5d:
8d:b9:45:b3:22:47:f7:c0:3d:b9:e5:a5:c8:2d:cf:
00:c5:5c:48:bb:dd:95:40:47:95:a3:54:ee:85:98:
14:1e:ad:15:59:20:cd:1e:48:9c:de:dc:09:55:8c:
5a:d4:b6:67:32:c3:55:ed:a6:26:c7:0f:67:03:c6:
3d:d8:c2:89:e4:d1:a6:92:c1:d4:71:01:ec:f6:ab:
31:88:64:26:70:15:8d:fa:20:5f:9f:b5:e8:f0:f7:
73:2d:20:0c:12:77:36:90:63:f3:7f:76:8d:64:7c:
3a:20:a2:67:35:10:90:83:8b:a6:90:8b:4d:45:7a:
70:95
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
F8:53:C1:EA:D3:29:34:D2:3F:B5:E5:ED:D0:C1:79:34:DA:13:BC:E8
X509v3 Authority Key Identifier:
keyid:F8:53:C1:EA:D3:29:34:D2:3F:B5:E5:ED:D0:C1:79:34:DA:13:BC:E8
X509v3 Basic Constraints:
CA:TRUE
Signature Algorithm: sha1WithRSAEncryption
1b:6b:1a:ea:04:75:29:1e:37:fd:17:b7:a7:2c:9d:4d:f8:8d:
00:e4:38:24:70:88:78:32:d2:41:04:c5:08:b2:25:b3:e3:c9:
16:2c:9f:9c:96:38:ae:e3:92:bd:5b:5a:c3:91:16:11:e3:56:
8d:b1:fb:0e:a7:cb:52:8e:d4:f8:a6:17:6d:f4:78:ef:24:2a:
0c:58:16:89:37:7f:aa:a4:52:51:96:44:ff:f5:ac:57:b3:fb:
50:13:e1:08:a0:79:c4:f0:8d:5d:f3:bd:a8:43:73:02:0e:a7:
18:7b:7c:e1:7c:6d:21:4b:0b:e2:2b:c6:70:81:10:ec:e9:b9:
db:e1:0e:fd:c3:54:4c:0a:f4:c7:4c:0a:c3:f3:f5:7e:d0:03:
31:1d:0a:a7:87:da:9d:78:35:de:30:cf:bb:d6:91:95:b5:7d:
dc:0e:fe:e4:db:68:90:8c:3a:ec:3f:57:57:ce:5f:07:c1:9b:
43:cb:39:d9:41:38:d7:55:10:f1:cd:74:70:ba:0a:11:8a:5f:
e8:e6:ef:98:da:fc:ff:09:a2:68:2b:e7:96:88:98:4b:0c:17:
0d:dc:59:3b:92:a1:23:ad:32:fc:1d:19:85:01:db:9d:ee:af:
b7:bb:c7:8a:c5:7b:2b:51:f0:44:00:b7:4c:df:8a:cd:ff:cc:
05:44:b0:79
-----BEGIN CERTIFICATE-----
MIIDyTCCArGgAwIBAgIJAJOlgKHg2bXCMA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNV
BAYTAlVTMQswCQYDVQQIDAJDQTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
dHkgTHRkMRowGAYDVQQDDBFub3RyZWFsbHkuZWZmLm9yZzEgMB4GCSqGSIb3DQEJ
ARYRbm90cmVhbGx5QGVmZi5vcmcwHhcNMTIwNzA3MjAwNzU5WhcNMTUwNzA3MjAw
NzU5WjB7MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExITAfBgNVBAoMGEludGVy
bmV0IFdpZGdpdHMgUHR5IEx0ZDEaMBgGA1UEAwwRbm90cmVhbGx5LmVmZi5vcmcx
IDAeBgkqhkiG9w0BCQEWEW5vdHJlYWxseUBlZmYub3JnMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAqBeUj2NVIUhzv99YZQj4I+sKbVMu9JKTBboLxfjG
zt3MZFMukGauhGNk06874fAcVzTCzfbs6s0Hsy5cMhNiqgasHUHuJnxvwderPs+P
SYkLvYl4py3GdJFvy3AKeeqzvSpY50QHkxmo4gYkvjxdbSUahfiWPvGzCIyGxQ8B
Dg80BtSUc12NuUWzIkf3wD255aXILc8AxVxIu92VQEeVo1TuhZgUHq0VWSDNHkic
3twJVYxa1LZnMsNV7aYmxw9nA8Y92MKJ5NGmksHUcQHs9qsxiGQmcBWN+iBfn7Xo
8PdzLSAMEnc2kGPzf3aNZHw6IKJnNRCQg4umkItNRXpwlQIDAQABo1AwTjAdBgNV
HQ4EFgQU+FPB6tMpNNI/teXt0MF5NNoTvOgwHwYDVR0jBBgwFoAU+FPB6tMpNNI/
teXt0MF5NNoTvOgwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAG2sa
6gR1KR43/Re3pyydTfiNAOQ4JHCIeDLSQQTFCLIls+PJFiyfnJY4ruOSvVtaw5EW
EeNWjbH7DqfLUo7U+KYXbfR47yQqDFgWiTd/qqRSUZZE//WsV7P7UBPhCKB5xPCN
XfO9qENzAg6nGHt84XxtIUsL4ivGcIEQ7Om52+EO/cNUTAr0x0wKw/P1ftADMR0K
p4fanXg13jDPu9aRlbV93A7+5NtokIw67D9XV85fB8GbQ8s52UE411UQ8c10cLoK
EYpf6ObvmNr8/wmiaCvnloiYSwwXDdxZO5KhI60y/B0ZhQHbne6vt7vHisV7K1Hw
RAC3TN+Kzf/MBUSweQ==
-----END CERTIFICATE-----

View File

@@ -1,18 +0,0 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIC0jCCAboCAQAwgYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEPMA0GA1UE
BwwGRnJpc2NvMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxGjAY
BgNVBAMMEW5vdHJlYWxseS5lZmYub3JnMSAwHgYJKoZIhvcNAQkBFhFub3RyZWFs
bHlAZWZmLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKgXlI9j
VSFIc7/fWGUI+CPrCm1TLvSSkwW6C8X4xs7dzGRTLpBmroRjZNOvO+HwHFc0ws32
7OrNB7MuXDITYqoGrB1B7iZ8b8HXqz7Pj0mJC72JeKctxnSRb8twCnnqs70qWOdE
B5MZqOIGJL48XW0lGoX4lj7xswiMhsUPAQ4PNAbUlHNdjblFsyJH98A9ueWlyC3P
AMVcSLvdlUBHlaNU7oWYFB6tFVkgzR5InN7cCVWMWtS2ZzLDVe2mJscPZwPGPdjC
ieTRppLB1HEB7ParMYhkJnAVjfogX5+16PD3cy0gDBJ3NpBj8392jWR8OiCiZzUQ
kIOLppCLTUV6cJUCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4IBAQAQc//VdI0lwSd4
lmSGFvOTaA7q4QDQsFw5qrc6JL5EnUY51nbipetna1N/sgOEHJEZbKfsxK0cgVb6
mbSG7qXKJ8HM7Xd/fLJTwmoDDFndlhDHIAmAOjA38RtzJKeeY0wLZZtPyGVMcxet
72BiLRBlsmjTQY/TwdL0mftDjvMpJUJVbTMt+jOFyS6RYRbTO83KXpk7PW70Xg13
TfngO7wnaFlmmtey6bRbNmFOLRVeRYslD1AfUbCU0cq5DGWJ8xZ+ifd+uzVcSeA6
chMXg4Hb3SzmPyqQCEHPa7FqNrkqlfTr+hvY0cu2SAdpIsN6L1qruo4I3AHYRjPD
i1xY9ZEo
-----END CERTIFICATE REQUEST-----

View File

@@ -1,2 +0,0 @@
V 150707200759Z 93A580A1E0D9B5C2 unknown /C=US/ST=CA/O=Internet Widgits Pty Ltd/CN=notreally.eff.org/emailAddress=notreally@eff.org
V 130707200943Z 93A580A1E0D9B5C3 unknown /C=US/ST=CA/L=San Francisco/O=I/OU=I/CN=example.com

View File

@@ -1 +0,0 @@
unique_subject = no

View File

@@ -1 +0,0 @@
unique_subject = yes

View File

@@ -1 +0,0 @@
V 150707200759Z 93A580A1E0D9B5C2 unknown /C=US/ST=CA/O=Internet Widgits Pty Ltd/CN=notreally.eff.org/emailAddress=notreally@eff.org

View File

@@ -1,81 +0,0 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
93:a5:80:a1:e0:d9:b5:c2
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=US, ST=CA, O=Internet Widgits Pty Ltd, CN=notreally.eff.org/emailAddress=notreally@eff.org
Validity
Not Before: Jul 7 20:07:59 2012 GMT
Not After : Jul 7 20:07:59 2015 GMT
Subject: C=US, ST=CA, O=Internet Widgits Pty Ltd, CN=notreally.eff.org/emailAddress=notreally@eff.org
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:a8:17:94:8f:63:55:21:48:73:bf:df:58:65:08:
f8:23:eb:0a:6d:53:2e:f4:92:93:05:ba:0b:c5:f8:
c6:ce:dd:cc:64:53:2e:90:66:ae:84:63:64:d3:af:
3b:e1:f0:1c:57:34:c2:cd:f6:ec:ea:cd:07:b3:2e:
5c:32:13:62:aa:06:ac:1d:41:ee:26:7c:6f:c1:d7:
ab:3e:cf:8f:49:89:0b:bd:89:78:a7:2d:c6:74:91:
6f:cb:70:0a:79:ea:b3:bd:2a:58:e7:44:07:93:19:
a8:e2:06:24:be:3c:5d:6d:25:1a:85:f8:96:3e:f1:
b3:08:8c:86:c5:0f:01:0e:0f:34:06:d4:94:73:5d:
8d:b9:45:b3:22:47:f7:c0:3d:b9:e5:a5:c8:2d:cf:
00:c5:5c:48:bb:dd:95:40:47:95:a3:54:ee:85:98:
14:1e:ad:15:59:20:cd:1e:48:9c:de:dc:09:55:8c:
5a:d4:b6:67:32:c3:55:ed:a6:26:c7:0f:67:03:c6:
3d:d8:c2:89:e4:d1:a6:92:c1:d4:71:01:ec:f6:ab:
31:88:64:26:70:15:8d:fa:20:5f:9f:b5:e8:f0:f7:
73:2d:20:0c:12:77:36:90:63:f3:7f:76:8d:64:7c:
3a:20:a2:67:35:10:90:83:8b:a6:90:8b:4d:45:7a:
70:95
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
F8:53:C1:EA:D3:29:34:D2:3F:B5:E5:ED:D0:C1:79:34:DA:13:BC:E8
X509v3 Authority Key Identifier:
keyid:F8:53:C1:EA:D3:29:34:D2:3F:B5:E5:ED:D0:C1:79:34:DA:13:BC:E8
X509v3 Basic Constraints:
CA:TRUE
Signature Algorithm: sha1WithRSAEncryption
1b:6b:1a:ea:04:75:29:1e:37:fd:17:b7:a7:2c:9d:4d:f8:8d:
00:e4:38:24:70:88:78:32:d2:41:04:c5:08:b2:25:b3:e3:c9:
16:2c:9f:9c:96:38:ae:e3:92:bd:5b:5a:c3:91:16:11:e3:56:
8d:b1:fb:0e:a7:cb:52:8e:d4:f8:a6:17:6d:f4:78:ef:24:2a:
0c:58:16:89:37:7f:aa:a4:52:51:96:44:ff:f5:ac:57:b3:fb:
50:13:e1:08:a0:79:c4:f0:8d:5d:f3:bd:a8:43:73:02:0e:a7:
18:7b:7c:e1:7c:6d:21:4b:0b:e2:2b:c6:70:81:10:ec:e9:b9:
db:e1:0e:fd:c3:54:4c:0a:f4:c7:4c:0a:c3:f3:f5:7e:d0:03:
31:1d:0a:a7:87:da:9d:78:35:de:30:cf:bb:d6:91:95:b5:7d:
dc:0e:fe:e4:db:68:90:8c:3a:ec:3f:57:57:ce:5f:07:c1:9b:
43:cb:39:d9:41:38:d7:55:10:f1:cd:74:70:ba:0a:11:8a:5f:
e8:e6:ef:98:da:fc:ff:09:a2:68:2b:e7:96:88:98:4b:0c:17:
0d:dc:59:3b:92:a1:23:ad:32:fc:1d:19:85:01:db:9d:ee:af:
b7:bb:c7:8a:c5:7b:2b:51:f0:44:00:b7:4c:df:8a:cd:ff:cc:
05:44:b0:79
-----BEGIN CERTIFICATE-----
MIIDyTCCArGgAwIBAgIJAJOlgKHg2bXCMA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNV
BAYTAlVTMQswCQYDVQQIDAJDQTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
dHkgTHRkMRowGAYDVQQDDBFub3RyZWFsbHkuZWZmLm9yZzEgMB4GCSqGSIb3DQEJ
ARYRbm90cmVhbGx5QGVmZi5vcmcwHhcNMTIwNzA3MjAwNzU5WhcNMTUwNzA3MjAw
NzU5WjB7MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExITAfBgNVBAoMGEludGVy
bmV0IFdpZGdpdHMgUHR5IEx0ZDEaMBgGA1UEAwwRbm90cmVhbGx5LmVmZi5vcmcx
IDAeBgkqhkiG9w0BCQEWEW5vdHJlYWxseUBlZmYub3JnMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAqBeUj2NVIUhzv99YZQj4I+sKbVMu9JKTBboLxfjG
zt3MZFMukGauhGNk06874fAcVzTCzfbs6s0Hsy5cMhNiqgasHUHuJnxvwderPs+P
SYkLvYl4py3GdJFvy3AKeeqzvSpY50QHkxmo4gYkvjxdbSUahfiWPvGzCIyGxQ8B
Dg80BtSUc12NuUWzIkf3wD255aXILc8AxVxIu92VQEeVo1TuhZgUHq0VWSDNHkic
3twJVYxa1LZnMsNV7aYmxw9nA8Y92MKJ5NGmksHUcQHs9qsxiGQmcBWN+iBfn7Xo
8PdzLSAMEnc2kGPzf3aNZHw6IKJnNRCQg4umkItNRXpwlQIDAQABo1AwTjAdBgNV
HQ4EFgQU+FPB6tMpNNI/teXt0MF5NNoTvOgwHwYDVR0jBBgwFoAU+FPB6tMpNNI/
teXt0MF5NNoTvOgwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAG2sa
6gR1KR43/Re3pyydTfiNAOQ4JHCIeDLSQQTFCLIls+PJFiyfnJY4ruOSvVtaw5EW
EeNWjbH7DqfLUo7U+KYXbfR47yQqDFgWiTd/qqRSUZZE//WsV7P7UBPhCKB5xPCN
XfO9qENzAg6nGHt84XxtIUsL4ivGcIEQ7Om52+EO/cNUTAr0x0wKw/P1ftADMR0K
p4fanXg13jDPu9aRlbV93A7+5NtokIw67D9XV85fB8GbQ8s52UE411UQ8c10cLoK
EYpf6ObvmNr8/wmiaCvnloiYSwwXDdxZO5KhI60y/B0ZhQHbne6vt7vHisV7K1Hw
RAC3TN+Kzf/MBUSweQ==
-----END CERTIFICATE-----

View File

@@ -1,83 +0,0 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
93:a5:80:a1:e0:d9:b5:c3
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=US, ST=CA, O=Internet Widgits Pty Ltd, CN=notreally.eff.org/emailAddress=notreally@eff.org
Validity
Not Before: Jul 7 20:09:43 2012 GMT
Not After : Jul 7 20:09:43 2013 GMT
Subject: C=US, ST=CA, L=San Francisco, O=I, OU=I, CN=example.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:cb:e5:99:e2:d7:c3:b4:01:10:63:6f:a0:5b:ce:
68:dc:06:6b:76:96:a2:b7:d7:a3:6e:1e:bf:48:f0:
0b:bd:d7:2b:08:46:56:2b:5d:55:60:47:44:c4:2c:
9f:3d:22:91:a7:97:02:3a:f4:af:88:b3:76:89:1b:
74:53:7b:b5:36:c6:28:a0:0d:cb:11:14:1c:8f:2f:
c7:ed:b0:b1:4a:49:0e:18:b4:f4:c5:0a:75:63:72:
f7:d4:68:c5:36:cb:8f:f2:11:db:48:0f:17:8f:cb:
4a:df:f0:6d:28:a5:c9:f3:33:b6:af:2c:3d:f3:5c:
88:32:39:d8:0e:e2:4d:23:2b:be:81:b0:2c:29:74:
b7:f8:61:81:a7:0b:e8:0f:0e:bf:8a:04:4b:bf:0a:
00:11:c6:f0:b7:83:95:6d:52:87:20:15:f7:2b:da:
da:ab:61:fc:94:14:f1:ba:f2:ad:7a:18:f1:b6:a2:
ba:13:2b:41:cd:4f:97:82:1f:c7:8e:29:7d:bb:86:
50:13:7e:78:6c:74:e6:9d:0b:65:1b:7e:b7:17:8b:
9f:2e:99:1e:d9:d8:54:5f:47:9c:c9:96:f8:86:3d:
87:41:fd:67:71:e9:48:17:30:d5:b2:ef:cc:9e:90:
a4:4c:32:fa:93:b2:21:66:92:95:62:5b:4d:e0:80:
ff:25
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Comment:
OpenSSL Generated Certificate
X509v3 Subject Key Identifier:
1A:3F:00:7C:09:C8:12:FC:EF:A8:E9:B5:71:EC:D0:A1:AA:20:1E:55
X509v3 Authority Key Identifier:
keyid:F8:53:C1:EA:D3:29:34:D2:3F:B5:E5:ED:D0:C1:79:34:DA:13:BC:E8
Signature Algorithm: sha1WithRSAEncryption
4b:db:6c:c8:4f:4c:be:54:e3:90:fb:9a:6e:9c:ec:4e:af:ca:
17:b5:3a:e7:d8:10:b1:cc:77:30:3d:20:3a:f7:b6:61:ce:1d:
62:09:a9:58:27:ab:35:ef:be:63:e4:18:3a:63:04:41:a9:99:
80:ba:1d:b1:1c:1d:9c:f7:0b:ca:3a:8b:86:ea:39:95:bf:ca:
27:1d:21:13:c8:c3:f0:a4:81:04:6f:6d:8f:8c:7d:ce:31:38:
d7:1f:05:3d:3b:05:3c:f0:da:e9:3c:b3:1b:36:4d:b7:39:82:
6e:42:8c:c5:05:02:2a:ab:3e:ef:09:34:1c:8b:08:26:d3:de:
4b:ee:a8:d5:25:ce:18:47:89:3f:0c:3c:04:03:a6:35:a3:21:
14:b6:fc:7a:04:76:b6:69:8b:ce:5c:90:34:f5:25:de:f1:c0:
20:a3:38:6d:c3:ef:b4:1a:36:8b:34:a6:91:f0:d6:be:60:39:
c6:b7:1b:00:da:80:dc:c0:cd:96:66:9c:d5:f9:f3:a2:47:6c:
bf:45:9f:98:41:3d:57:9a:aa:0a:87:1b:a7:d2:48:60:a3:5d:
2d:45:4f:6f:e1:8b:5b:14:93:73:10:d8:d7:ff:ed:50:87:36:
70:23:b3:e5:5e:4d:1c:21:76:3c:c7:b5:a5:da:fc:19:a6:8d:
0e:5e:dd:14
-----BEGIN CERTIFICATE-----
MIID2TCCAsGgAwIBAgIJAJOlgKHg2bXDMA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNV
BAYTAlVTMQswCQYDVQQIDAJDQTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
dHkgTHRkMRowGAYDVQQDDBFub3RyZWFsbHkuZWZmLm9yZzEgMB4GCSqGSIb3DQEJ
ARYRbm90cmVhbGx5QGVmZi5vcmcwHhcNMTIwNzA3MjAwOTQzWhcNMTMwNzA3MjAw
OTQzWjBgMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBG
cmFuY2lzY28xCjAIBgNVBAoTAUkxCjAIBgNVBAsTAUkxFDASBgNVBAMTC2V4YW1w
bGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy+WZ4tfDtAEQ
Y2+gW85o3AZrdpait9ejbh6/SPALvdcrCEZWK11VYEdExCyfPSKRp5cCOvSviLN2
iRt0U3u1NsYooA3LERQcjy/H7bCxSkkOGLT0xQp1Y3L31GjFNsuP8hHbSA8Xj8tK
3/BtKKXJ8zO2ryw981yIMjnYDuJNIyu+gbAsKXS3+GGBpwvoDw6/igRLvwoAEcbw
t4OVbVKHIBX3K9raq2H8lBTxuvKtehjxtqK6EytBzU+Xgh/Hjil9u4ZQE354bHTm
nQtlG363F4ufLpke2dhUX0ecyZb4hj2HQf1ncelIFzDVsu/MnpCkTDL6k7IhZpKV
YltN4ID/JQIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVu
U1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUGj8AfAnIEvzvqOm1
cezQoaogHlUwHwYDVR0jBBgwFoAU+FPB6tMpNNI/teXt0MF5NNoTvOgwDQYJKoZI
hvcNAQEFBQADggEBAEvbbMhPTL5U45D7mm6c7E6vyhe1OufYELHMdzA9IDr3tmHO
HWIJqVgnqzXvvmPkGDpjBEGpmYC6HbEcHZz3C8o6i4bqOZW/yicdIRPIw/CkgQRv
bY+Mfc4xONcfBT07BTzw2uk8sxs2Tbc5gm5CjMUFAiqrPu8JNByLCCbT3kvuqNUl
zhhHiT8MPAQDpjWjIRS2/HoEdrZpi85ckDT1Jd7xwCCjOG3D77QaNos0ppHw1r5g
Oca3GwDagNzAzZZmnNX586JHbL9Fn5hBPVeaqgqHG6fSSGCjXS1FT2/hi1sUk3MQ
2Nf/7VCHNnAjs+VeTRwhdjzHtaXa/BmmjQ5e3RQ=
-----END CERTIFICATE-----

View File

@@ -1,30 +0,0 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQILkKoXhp8VbACAggA
MBQGCCqGSIb3DQMHBAgNTj9llxbfLwSCBMhNvE0ThgFyGPghkO3647mzazCU0p1w
kHaGZieGk7RHFWcG1M0SQ4uxJ7o6mQSDdcBdhoqqph5mjrEor8lOgNXGGd3UMJxH
Z6VxXriGxSpfF1gQS+z6tJKa9bNJBSAXMhrwc1iVYMxhxvcdO5FTCr0wOJQfEOQR
T2rwxgIHtHhVEVRTO+VSu1OKEFTGZME8LIa1exaE9aORlfRwm9IBlCSOzg7LefNi
nVBhLD8iMPFcUy82mlloMlN1Q6lJAKzlsm86VA3OMDIQt6SZlc9v6L6BV8CZTR7o
48TiRwBi4z6/MLakTrUuiPFSfd2vSbKwj8rZZ9ZKKfFGyYfrTkx6uaBC2lYKfTSz
kAmU970N/JuCDGfFS16VF4coIWlI7WNflhxdDUu/O5cnWL/NTFQBHcZBheTv9Pzr
/9Ab5x41fqhq0llQjCb2rVZ9J86S8ffX4YEoApqtZNhnn4yRhX2BNr29VpjZy5kN
lRAZemgPZ3B3XosQeY7vaVqzB1TxCtbi9N5wEk0TI3F3i64Xj30qEuAaMRXTXTVZ
HZJSUZ8rYrBe9Hvhg/6ckm7mTtyD52D0RpgY7iCwi13qh+ZhbJ42VIYgxupO7/F1
EKcQLYj8bJzZ5VGPZnrfX51rQnfYzodXx822wt3zbdjJQl5e+diHU+ROBipVxf6y
XeD6j6uVfGbFxpOmP27WEoF/jUDjBcIyI5k5SlcMV71PFQjKlh5Z7fxeNKCrV1hD
PfHtL73dbVvibUkoTKVWPUlgPuTWanTvjpGn0rpqCq24WpayJSm1PcKxMMQzMcvh
90bqRL0PgKPjIf+w8uk/LRX8RMtFGnQ7YCkET0utq33tjgKPiervgXLsXo/eZuK2
RHPfYaeRy0MP4tmtOPIJJJ9eG7CC8S9pvqkknOA5N2jSrvNJLog+RAbFJOUruUgH
pSJCqk6QkQ+n7hGcL4PZXJ49A4VKt8i1MfOSnlnNXKctiZUS1HNS6VOvRhkbCRX2
FZHiupfuzmXAgf0NBEoltPxLvAC4aeomIO/6LGUf3OIoBDN8Ul8NXW0Nf9iZn2kJ
/Wd55syza1KR3JGtvUl6+hdod/m7mH5z3qZV1x0LfncRsQxSNmCydzA7CqU7yINt
MF786roXhgvcbTAuvxexpw4/QtWUHkRxidmifxlq9Xf6jK6yPiMQVX4HkwixMxFY
OGf8SCCRYBhwWbrs3B3IJ6i/iND5a7lZcNSCz9WvLECxmXq0bume5UTW1g+9vnPV
f8zxeWC4OJTNmu+iH1KwxJXpAb+JqmeHPdFlN03QQLrHw0TyFBvrJv7/mU+2PYSM
AhjIgSn/jdSl5yDq+Tw6Hua2SaV8k7RGFWU40MlHaMk4dC5+aQ8wVVVhOA7agVWI
iakA0XJm67JWtJCJUn66dexQqC5YtLp5SNC8Kl/xS8o4tDeMCpPOydtiVoiZKlTV
WA7qdd/o08vsmyOX68XgcbERhm9//QxRt/ygXni9ygUMmmJYx1QGcSUV6XEJYe/j
/ZY35N2bBvJmmNOZr9jZihQkt2I+Js91A5I3HhSh4aXqt+hBzp9TcvLRvDiRcOSs
H0rVWeNWmt4EqTFRzEXv7w+hmTgchxyFZiSIiZjy4U53wT4taTGJAsTW7wvnbnsz
JMw=
-----END ENCRYPTED PRIVATE KEY-----

View File

@@ -1 +0,0 @@
93A580A1E0D9B5C4

View File

@@ -1 +0,0 @@
93A580A1E0D9B5C3

View File

@@ -1,81 +0,0 @@
<!DOCTYPE html>
<html class="no-js">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Project Chocolate</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="/css/normalize.min.css">
<link rel="stylesheet" href="/css/main.css">
<!--[if lt IE 9]>
<script src="/js/vendor/html5.js"></script>
<script>window.html5 || document.write('<script src="/js/vendor/html5shiv.js"><\/script>')</script>
<![endif]-->
</head>
<body>
<div class="header-container">
<header class="wrapper clearfix">
<h1 class="title">Payment Required</h1>
<nav>
<ul>
<li><a href="#"></a></li>
<li><a href="#"></a></li>
<li><a href="#"></a></li>
</ul>
</nav>
</header>
</div>
<div class="main-container">
<div class="main wrapper clearfix">
<article>
<header>
</header>
<section>
<h2>Payment Form</h2>
<form method="get" accept-charset="UTF-8" action="/payment.py/submit=%s">
<p><label for="name">Name:</label> <input name="" type="text" autocomplete="off" value="John Doe" id="name" size="20" maxlength="30" /></p>
<p><label for="card-type">Credit card type:</label> <select name=""><option>Vista</option><option>MisterCard</option><option>Discovery</option></select></p>
<p><label for="credit-card">Credit card number:</label> <input name="" type="text" autocomplete="off" id="credit-card" size="20" style="font-family:monospace" maxlength="16" /></p>
<p><input type="submit" value="Submit" /></p>
</form>
</p>
</section>
<footer>
</footer>
</article>
<aside>
<p>
<b>Payment required</b>
</p><p>
Due to certificate authority policy, issuing this certificate requires a payment.
<p>
<hr width="70%%" />
<p>
A payment of <b>17.00 simoleons</b> is due now.
<p>
In order to process this payment, please pretend to enter a 16-digit credit-card
number, and then click the Submit Payment button.</p>
<p>
This payment will appear on your
credit card statement as TRUSTIFIABLE CERTIFICATE SERVICES.</p>
</aside>
</div> <!-- #main -->
</div> <!-- #main-container -->
<div class="footer-container">
<footer class="wrapper">
</footer>
</div>
<script src="/js/main.js"></script>
</body>
</html>

View File

@@ -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 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, random, random_raw, log
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.
log("removing expired (issue-state!?) session", 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)
log("attempting to issue certificate for names: %s" % ", ".join(names), session)
with issue_lock:
cert = CSR.issue(csr, names)
r.hset(session, "cert", cert)
if cert: # once issuing cert succeeded
log("issued certificate for names: %s" % ", ".join(names), session)
r.hset(session, "state", "done")
# r.lpush("pending-done", session)
else: # should not be reached in deployed version
log("issuing cert failed!?", session)
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 "issue daemon exiting cleanly"
break

View File

@@ -1,31 +0,0 @@
#!/usr/bin/env python
# This daemon runs on the CA side to handle logging.
import redis, signal, sys, time
r = redis.Redis()
ps = r.pubsub()
debug = "debug" in sys.argv
clean_shutdown = False
from daemon_common import signal_handler, log
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":
sys.stdout.write(time.strftime("%b %d %H:%M:%S") + " " + message["data"] + "\n")
sys.stdout.flush()
continue
if message["channel"] == "exit":
break
if clean_shutdown:
sys.stdout.write("logging daemon exiting cleanly\n")
sys.stdout.flush()
break

View File

@@ -1,69 +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, time, sys, signal
r = redis.Redis()
ps = r.pubsub()
debug = "debug" in sys.argv
clean_shutdown = False
from daemon_common import signal_handler, short, random, random_raw, log
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.
log("removing expired session", 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)
log("new valid request from requesting client at %s" % r.hget(session, "client-addr"), session)
log("for %d names: %s" % (len(names), ", ".join(names)), session)
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)
log("created new challenge %s" % short(challenge), session)
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 "makechallenge daemon exiting cleanly"
break

View File

@@ -1,65 +0,0 @@
#!/usr/bin/env python
# Wait for news about payments received for sesssions and
# then mark the sessions to show that that payment was received.
# The reason that this is separate from payment.py (which
# simulates actually processing a credit card payment) is
# to make the security analysis simpler and cleaner and
# reduce attack surface. The idea is that payment.py decides
# whether someone has paid, but NOT whether the certificate
# has been granted. This daemon decides whether the
# certificate should be granted, but NOT whether someone has
# paid. Thus, payment.py does not need, or exercise, the
# power to change session status directly.
# This preserves the rule that session status is only ever
# advanced by the appropriate daemon (though a session may
# be killed by any part of the system that identifies a
# fatal problem or protocol violation).
# This daemon uses a different scheduling model from the
# testchallenge daemon so ONLY ONE COPY OF THIS DAEMON SHOULD
# BE RUN AT ONCE. Since this daemon takes a minimal, discrete
# action in response to a pubsub message, there should never be
# a significant backlog associated with this daemon.
import redis, signal, sys
r = redis.Redis()
ps = r.pubsub()
debug = "debug" in sys.argv
clean_shutdown = False
from daemon_common import signal_handler, short, log
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
ps.subscribe(["payments", "exit"])
for message in ps.listen():
if message["type"] != "message":
continue
if message["channel"] == "payments":
if debug: print message["data"]
session = message["data"]
if len(session) != 64:
log("received payment request for weird session")
continue
if session not in r or r.hget(session, "live") != "True":
log("received payment request for nonexistent or inactive session")
continue
if r.hget(session, "state") != "payment":
log("received untimely payment request")
continue
log("received valid payment notification for", session)
log("\t** All challenges satisfied; payment received; request GRANTED", session)
r.hset(session, "state", "issue")
r.lpush("pending-issue", session)
continue
if message["channel"] == "exit":
break
if clean_shutdown:
print "payment daemon exiting cleanly"
break

View File

@@ -1,54 +0,0 @@
#!/usr/bin/env python
# TODO: Is there some way to limit this program's access to the database
# so that it cannot change any values, but can still publish pubsub
# messages? That would make the security analysis of the system as a
# whole clearer.
import web, redis
urls = (
'/([a-f0-9]{10})', 'shortform',
'/submit=([a-f0-9]{64})', 'payment'
)
r = redis.Redis()
class shortform(object):
def GET(self, what):
web.header("Content-type", "text/html")
if len(what) != 10 or not all(hexdigit(s) for s in what):
return "<html><h1>Unknown session ID</h1></html>"
expanded = r.get("shorturl-%s" % what)
if not expanded:
return "<html><h1>Unknown session ID</h1></html>"
with open("index.html","r") as f:
return f.read() % expanded
def hexdigit(s):
return s in "0123456789abcdef"
def log(msg):
r.publish("logs", msg)
class payment(object):
def GET(self, session):
web.header("Content-type", "text/html")
if len(session) != 64 or not all(hexdigit(s) for s in session):
return "<html><h1>Oops!</h1>Attempt to process payment for invalid session.</html>"
if session not in r or r.hget(session, "live") != "True":
return "<html><h1>Oops!</h1>Attempt to process payment for invalid session.</html>"
if r.hget(session, "state") != "payment":
return "<html><h1>Oops!</h1>Attempt to process payment for session that was not expecting it.</html>"
r.publish("payments", session)
names = r.lrange("%s:names" % session, 0, -1)
names_list = '<ul style="font-family:monospace">' + "\n".join("<li>%s</li>" % n for n in names) + '</ul>'
log("received valid payment details from %s" % web.ctx.ip)
with open("thanks.html","r") as f:
return f.read() % (session, names_list)
if __name__ == "__main__":
app = web.application(urls, globals())
app.run()
# vim: set tabstop=4 shiftwidth=4 expandtab

View File

@@ -1,89 +0,0 @@
#!/usr/bin/env python
# This file should contain functions that set CA-side policies (that
# could change over time or differ from CA to CA) on whether individual
# aspects of a session are legitimate or appropriate.
# Functions here can access Redis if necessary to examine details of
# a session.
# Examples: session expiry times
import redis
r = redis.Redis()
def payment_required(session):
"""Does this session require a payment?"""
# Sample policy: require a payment when total number of requested
# subject names is greater than one.
#if r.llen("%s:names" % session) > 1:
# return True
# Second example: if any of the names are in the Alexa or Quantcast top
# 10,000, call for a payment
names = r.lrange("%s:names" % session, 0, -1)
for name in names:
if in_top_10k(name): return True
return False
def in_top_10k(hostname):
"""Check whether a hostname is part of a top 10,000 website."""
# That includes subdomains of top 10,000 sites, but not if the subdomain
# is below a public suffix (such as a dynamic DNS provider or hosting
# umbrella, perhaps)
parts = hostname.lower().split(".")
for n in range(2, len(parts)+1):
name_or_parent = ".".join(parts[-n:])
if name_or_parent in top_10k:
return True
# XXX if name_or_parent in public_suffix_list: break
return False
def check_domain(domain):
import string as s
allowed = s.ascii_letters + s.digits + "-."
# top 10k domains should contain dots, and ASCII characters (for the TLD,
# if nothing else).
# XXX The Alexa top10k contains a few IP addresses. This currently
# excludes them, but perhaps it shouldn't...
if len([c for c in domain if c in s.ascii_letters]) == 0: return False
if "." not in domain: return False
return all([c in allowed for c in domain])
have_top_10k = False
def get_top_10k():
data_files = ["data/alexa-top-10k.txt","data/quantast-top-10k.txt"]
global top_10k, have_top_10k
top_10k = {}
for f in data_files:
for line in open(f).readlines():
domain=line.split()[1]
if check_domain(domain):
top_10k[domain] = True
have_top_10k = True
get_top_10k()
def expire_session(session, state):
"""Should this session be expired?"""
# Different maximum age policies apply to sessions that are waiting
# for a payment, and, in general, to sessions at different stages
# of their lifecycle.
# """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
if state == "testpayment" and age > 5000:
if debug: print "considered", short(session), "ancient"
return True
return False

View File

@@ -1,80 +0,0 @@
#!/usr/bin/env python
# This is an attempt at implementing the locking algorithm described at
# http://redis.io/commands/setnx
# as a Python lock object that can be used with the Python "with"
# statement. To use:
#
# lock = redis_lock(redis_instance, "name")
# with lock:
# # do stuff guarded by the lock
#
# Only one process will be able to enter the block at a time for a
# given Redis instance and name, as long as the most recent process
# to enter the block did so less than timeout seconds ago. All
# processes attempting to acquire the lock will poll to see if it
# is released or expires. If the algorithm is correct and correctly
# implemented, only one process succeds in clearing and acquiring a
# particular expired lock, even "when multiple clients detected an
# expired lock and are trying to release it".
#
# The optional one_shot parameter causes the attempt to acquire the
# lock to instead raise a KeyError exception if someone else is already
# holding a valid lock. This is used in situations where a process
# doesn't insist on doing the actions guarded by the lock.
import time, random
timeout = 60
def valid(t):
"""Is a lock with expiry time t now valid (not expired)?"""
return float(t) > time.time()
class redis_lock(object):
def __init__(self, redis, lock_name, one_shot=False):
self.redis = redis
self.lock_name = lock_name
self.one_shot = one_shot
def __enter__(self):
while True:
self.expiry = time.time() + timeout
# "C4 sends SETNX lock.foo in order to acquire the lock"
if self.redis.setnx(self.lock_name, self.expiry + 1):
return
# "C4 sends GET lock.foo to check if the lock expired."
existing_lock = self.redis.get(self.lock_name)
if (not existing_lock) or valid(existing_lock):
if self.one_shot:
raise KeyError
# "If it is not, it will sleep for some time and retry from
# the start."
time.sleep(1 + random.random())
continue
else:
# "Instead, if the lock is expired because the Unix time at
# lock.foo is older than the current Unix time, C4 tries to
# perform: GETSET lock.foo [...]"
result = self.redis.getset(self.lock_name, self.expiry + 1)
if not valid(result):
# "C4 can check if the old value stored at key is still
# an expired timestamp. If it is, the lock was acquired."
return
else:
# "If another client [...] was faster than C4 and acquired
# the lock with the GETSET operation, the C4 GETSET
# operation will return a non expired timestamp. C4 will
# simply restart from the first step."
continue
def __exit__(self, exception_type, exception_value, traceback):
# "[...] a client holding a lock should always check the timeout
# didn't expire before unlocking the key with DEL [...]"
if valid(self.expiry):
self.redis.delete(self.lock_name)
# This may be redundant. We have the ability to cancel exceptions
# that occur inside the with block, but we currently don't exercise
# this ability.
if exception_value is None:
return True

View File

@@ -1,5 +0,0 @@
_sni_support.so
sni_support_wrap.c
sni_support.py
sni_support.o
sni_support_wrap.o

View File

@@ -1,15 +0,0 @@
CFLAGS+=-fpic -I/usr/include/python2.7
LDFLAGS+=
targets=_sni_support.so sni_support_wrap.c sni_support.py sni_support.o sni_support_wrap.o
all: _sni_support.so
sni_support_wrap.c sni_support.py: sni_support.i
swig -python $^
_sni_support.so: sni_support_wrap.o sni_support.o
$(CC) -shared $^ -o $@
clean:
rm -f $(targets) *.pyc

View File

@@ -1,5 +0,0 @@
Run Make
Verify Challenge: make a call to verify_challenge(address, r, nonce)
Nonce and r must match up to original challenge values.
Example code is given in main().

View File

@@ -1,17 +0,0 @@
#!/usr/bin/env python
import exit_geography, random, socks
# This file is currently unused. It demonstrates how to make a
# connection using exit_geography and a Tor exit node in a chosen
# country. This can be used to implement multipath probing to
# perform SNI challenges from the vantage point of specified
# countries.
# NOTE: This requires a modification to your torrc: AllowDotExit 1
node = random.choice(exit_geography.by_country["DE"])
socksocket = socks.socksocket()
socksocket.setproxy(socks.PROXY_TYPE_SOCKS4, "localhost", 9050)
print node
socksocket.connect(("theobroma.info.%s.exit" % node, 80))

View File

@@ -1,24 +0,0 @@
#!/usr/bin/env python
import pygeoip, random
geoip = pygeoip.GeoIP('GeoIP.dat', pygeoip.MEMORY_CACHE)
allrouters = []
exits = []
by_country = {}
for L in open("/var/lib/tor/cached-consensus"):
if L.startswith("s "):
flags = L.strip().split()
if "Exit" in flags and "BadExit" not in flags and "Running" in flags and "Valid" in flags and "Stable" in flags:
exits.append((router[1], router[6], flags))
if L.startswith("r "):
router = L.strip().split()
allrouters.append(router[1])
duplicates = set(e[0] for e in exits if allrouters.count(e[0]) != 1)
for exit in exits:
name, ip, flags = exit
if name not in duplicates:
by_country.setdefault(geoip.country_code_by_addr(ip), []).append(name)

View File

@@ -1,577 +0,0 @@
"""
Pure Python GeoIP API. The API is based off of U{MaxMind's C-based Python API<http://www.maxmind.com/app/python>},
but the code itself is based on the U{pure PHP5 API<http://pear.php.net/package/Net_GeoIP/>}
by Jim Winstead and Hans Lellelid.
It is mostly a drop-in replacement, except the
C{new} and C{open} methods are gone. You should instantiate the L{GeoIP} class yourself:
C{gi = GeoIP('/path/to/GeoIP.dat', pygeoip.MEMORY_CACHE)}
@author: Jennifer Ennis <zaylea at gmail dot com>
@license:
Copyright(C) 2004 MaxMind LLC
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/lgpl.txt>.
"""
from __future__ import with_statement
import os
import math
import socket
import mmap
from const import *
from util import ip2long
class GeoIPError(Exception):
pass
class GeoIPMetaclass(type):
def __new__(cls, *args, **kwargs):
"""
Singleton method to gets an instance without reparsing the db. Unique
instances are instantiated based on the filename of the db. Flags are
ignored for this, i.e. if you initialize one with STANDARD flag (default)
and then try later to initialize with MEMORY_CACHE, it will still
return the STANDARD one.
"""
if not hasattr(cls, '_instances'):
cls._instances = {}
if len(args) > 0:
filename = args[0]
elif 'filename' in kwargs:
filename = kwargs['filename']
if not filename in cls._instances:
cls._instances[filename] = type.__new__(cls, *args, **kwargs)
return cls._instances[filename]
GeoIPBase = GeoIPMetaclass('GeoIPBase', (object,), {})
class GeoIP(GeoIPBase):
def __init__(self, filename, flags=0):
"""
Initialize the class.
@param filename: path to a geoip database
@type filename: str
@param flags: flags that affect how the database is processed.
Currently the only supported flags are STANDARD, MEMORY_CACHE, and
MMAP_CACHE.
@type flags: int
"""
self._filename = filename
self._flags = flags
if self._flags & MMAP_CACHE:
with open(filename, 'rb') as f:
self._filehandle = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
else:
self._filehandle = open(filename, 'rb')
if self._flags & MEMORY_CACHE:
self._memoryBuffer = self._filehandle.read()
self._setup_segments()
def _setup_segments(self):
"""
Parses the database file to determine what kind of database is being used and setup
segment sizes and start points that will be used by the seek*() methods later.
"""
self._databaseType = COUNTRY_EDITION
self._recordLength = STANDARD_RECORD_LENGTH
filepos = self._filehandle.tell()
self._filehandle.seek(-3, os.SEEK_END)
for i in range(STRUCTURE_INFO_MAX_SIZE):
delim = self._filehandle.read(3)
if delim == (chr(255) * 3):
self._databaseType = ord(self._filehandle.read(1))
if (self._databaseType >= 106):
# backwards compatibility with databases from April 2003 and earlier
self._databaseType -= 105
if self._databaseType == REGION_EDITION_REV0:
self._databaseSegments = STATE_BEGIN_REV0
elif self._databaseType == REGION_EDITION_REV1:
self._databaseSegments = STATE_BEGIN_REV1
elif self._databaseType in (CITY_EDITION_REV0,
CITY_EDITION_REV1,
ORG_EDITION,
ISP_EDITION,
ASNUM_EDITION):
self._databaseSegments = 0
buf = self._filehandle.read(SEGMENT_RECORD_LENGTH)
for j in range(SEGMENT_RECORD_LENGTH):
self._databaseSegments += (ord(buf[j]) << (j * 8))
if self._databaseType in (ORG_EDITION, ISP_EDITION):
self._recordLength = ORG_RECORD_LENGTH
break
else:
self._filehandle.seek(-4, os.SEEK_CUR)
if self._databaseType == COUNTRY_EDITION:
self._databaseSegments = COUNTRY_BEGIN
self._filehandle.seek(filepos, os.SEEK_SET)
def _lookup_country_id(self, addr):
"""
Get the country index.
This method is called by the _lookupCountryCode and _lookupCountryName
methods. It looks up the index ('id') for the country which is the key
for the code and name.
@param addr: The IP address
@type addr: str
@return: network byte order 32-bit integer
@rtype: int
"""
ipnum = ip2long(addr)
if not ipnum:
raise ValueError("Invalid IP address: %s" % addr)
if self._databaseType != COUNTRY_EDITION:
raise GeoIPError('Invalid database type; country_* methods expect '\
'Country database')
return self._seek_country(ipnum) - COUNTRY_BEGIN
def _seek_country(self, ipnum):
"""
Using the record length and appropriate start points, seek to the
country that corresponds to the converted IP address integer.
@param ipnum: result of ip2long conversion
@type ipnum: int
@return: offset of start of record
@rtype: int
"""
offset = 0
for depth in range(31, -1, -1):
if self._flags & MEMORY_CACHE:
startIndex = 2 * self._recordLength * offset
length = 2 * self._recordLength
endIndex = startIndex + length
buf = self._memoryBuffer[startIndex:endIndex]
else:
self._filehandle.seek(2 * self._recordLength * offset, os.SEEK_SET)
buf = self._filehandle.read(2 * self._recordLength)
x = [0,0]
for i in range(2):
for j in range(self._recordLength):
x[i] += ord(buf[self._recordLength * i + j]) << (j * 8)
if ipnum & (1 << depth):
if x[1] >= self._databaseSegments:
return x[1]
offset = x[1]
else:
if x[0] >= self._databaseSegments:
return x[0]
offset = x[0]
raise Exception('Error traversing database - perhaps it is corrupt?')
def _get_org(self, ipnum):
"""
Seek and return organization (or ISP) name for converted IP addr.
@param ipnum: Converted IP address
@type ipnum: int
@return: org/isp name
@rtype: str
"""
seek_org = self._seek_country(ipnum)
if seek_org == self._databaseSegments:
return None
record_pointer = seek_org + (2 * self._recordLength - 1) * self._databaseSegments
self._filehandle.seek(record_pointer, os.SEEK_SET)
org_buf = self._filehandle.read(MAX_ORG_RECORD_LENGTH)
return org_buf[:org_buf.index(chr(0))]
def _get_region(self, ipnum):
"""
Seek and return the region info (dict containing country_code and region_name).
@param ipnum: converted IP address
@type ipnum: int
@return: dict containing country_code and region_name
@rtype: dict
"""
country_code = ''
region = ''
if self._databaseType == REGION_EDITION_REV0:
seek_country = self._seek_country(ipnum)
seek_region = seek_country - STATE_BEGIN_REV0
if seek_region >= 1000:
country_code = 'US'
region = ''.join([chr((seek_region / 1000) / 26 + 65), chr((seek_region / 1000) % 26 + 65)])
else:
country_code = COUNTRY_CODES[seek_region]
region = ''
elif self._databaseType == REGION_EDITION_REV1:
seek_country = self._seek_country(ipnum)
seek_region = seek_country - STATE_BEGIN_REV1
if seek_region < US_OFFSET:
country_code = '';
region = ''
elif seek_region < CANADA_OFFSET:
country_code = 'US'
region = ''.join([chr((seek_region - US_OFFSET) / 26 + 65), chr((seek_region - US_OFFSET) % 26 + 65)])
elif seek_region < WORLD_OFFSET:
country_code = 'CA'
region = ''.join([chr((seek_region - CANADA_OFFSET) / 26 + 65), chr((seek_region - CANADA_OFFSET) % 26 + 65)])
else:
i = (seek_region - WORLD_OFFSET) / FIPS_RANGE
if i in COUNTRY_CODES:
country_code = COUNTRY_CODES[(seek_region - WORLD_OFFSET) / FIPS_RANGE]
else:
country_code = ''
region = ''
elif self._databaseType in (CITY_EDITION_REV0, CITY_EDITION_REV1):
rec = self._get_record(ipnum)
country_code = rec['country_code']
region = rec['region_name']
return {'country_code' : country_code, 'region_name' : region }
def _get_record(self, ipnum):
"""
Populate location dict for converted IP.
@param ipnum: converted IP address
@type ipnum: int
@return: dict with country_code, country_code3, country_name,
region, city, postal_code, latitude, longitude,
dma_code, metro_code, area_code, region_name, time_zone
@rtype: dict
"""
seek_country = self._seek_country(ipnum)
if seek_country == self._databaseSegments:
return None
record_pointer = seek_country + (2 * self._recordLength - 1) * self._databaseSegments
self._filehandle.seek(record_pointer, os.SEEK_SET)
record_buf = self._filehandle.read(FULL_RECORD_LENGTH)
record = {}
record_buf_pos = 0
char = ord(record_buf[record_buf_pos])
record['country_code'] = COUNTRY_CODES[char]
record['country_code3'] = COUNTRY_CODES3[char]
record['country_name'] = COUNTRY_NAMES[char]
record_buf_pos += 1
str_length = 0
# get region
char = ord(record_buf[record_buf_pos+str_length])
while (char != 0):
str_length += 1
char = ord(record_buf[record_buf_pos+str_length])
if str_length > 0:
record['region_name'] = record_buf[record_buf_pos:record_buf_pos+str_length]
record_buf_pos += str_length + 1
str_length = 0
# get city
char = ord(record_buf[record_buf_pos+str_length])
while (char != 0):
str_length += 1
char = ord(record_buf[record_buf_pos+str_length])
if str_length > 0:
record['city'] = record_buf[record_buf_pos:record_buf_pos+str_length]
record_buf_pos += str_length + 1
str_length = 0
# get the postal code
char = ord(record_buf[record_buf_pos+str_length])
while (char != 0):
str_length += 1
char = ord(record_buf[record_buf_pos+str_length])
if str_length > 0:
record['postal_code'] = record_buf[record_buf_pos:record_buf_pos+str_length]
else:
record['postal_code'] = None
record_buf_pos += str_length + 1
str_length = 0
latitude = 0
longitude = 0
for j in range(3):
char = ord(record_buf[record_buf_pos])
record_buf_pos += 1
latitude += (char << (j * 8))
record['latitude'] = (latitude/10000.0) - 180.0
for j in range(3):
char = ord(record_buf[record_buf_pos])
record_buf_pos += 1
longitude += (char << (j * 8))
record['longitude'] = (longitude/10000.0) - 180.0
if self._databaseType == CITY_EDITION_REV1:
dmaarea_combo = 0
if record['country_code'] == 'US':
for j in range(3):
char = ord(record_buf[record_buf_pos])
record_buf_pos += 1
dmaarea_combo += (char << (j*8))
record['dma_code'] = int(math.floor(dmaarea_combo/1000))
record['area_code'] = dmaarea_combo%1000
else:
record['dma_code'] = 0
record['area_code'] = 0
return record
def country_code_by_addr(self, addr):
"""
Returns 2-letter country code (e.g. 'US') for specified IP address.
Use this method if you have a Country, Region, or City database.
@param addr: IP address
@type addr: str
@return: 2-letter country code
@rtype: str
"""
try:
if self._databaseType == COUNTRY_EDITION:
country_id = self._lookup_country_id(addr)
return COUNTRY_CODES[country_id]
elif self._databaseType in (REGION_EDITION_REV0, REGION_EDITION_REV1,
CITY_EDITION_REV0, CITY_EDITION_REV1):
return self.region_by_addr(addr)['country_code']
else:
raise GeoIPError('Invalid database type; country_* methods expect '\
'Country, City, or Region database')
except ValueError:
raise GeoIPError('*_by_addr methods only accept IP addresses. Use *_by_name for hostnames. (Address: %s)' % addr)
def country_code_by_name(self, hostname):
"""
Returns 2-letter country code (e.g. 'US') for specified hostname.
Use this method if you have a Country, Region, or City database.
@param hostname: host name
@type hostname: str
@return: 2-letter country code
@rtype: str
"""
addr = socket.gethostbyname(hostname)
return self.country_code_by_addr(addr)
def country_name_by_addr(self, addr):
"""
Returns full country name for specified IP address.
Use this method if you have a Country or City database.
@param addr: IP address
@type addr: str
@return: country name
@rtype: str
"""
try:
if self._databaseType == COUNTRY_EDITION:
country_id = self._lookup_country_id(addr)
return COUNTRY_NAMES[country_id]
elif self._databaseType in (CITY_EDITION_REV0, CITY_EDITION_REV1):
return self.record_by_addr(addr)['country_name']
else:
raise GeoIPError('Invalid database type; country_* methods expect '\
'Country or City database')
except ValueError:
raise GeoIPError('*_by_addr methods only accept IP addresses. Use *_by_name for hostnames. (Address: %s)' % addr)
def country_name_by_name(self, hostname):
"""
Returns full country name for specified hostname.
Use this method if you have a Country database.
@param hostname: host name
@type hostname: str
@return: country name
@rtype: str
"""
addr = socket.gethostbyname(hostname)
return self.country_name_by_addr(addr)
def org_by_addr(self, addr):
"""
Lookup the organization (or ISP) for given IP address.
Use this method if you have an Organization/ISP database.
@param addr: IP address
@type addr: str
@return: organization or ISP name
@rtype: str
"""
try:
ipnum = ip2long(addr)
if not ipnum:
raise ValueError("Invalid IP address: %s" % addr)
if self._databaseType not in (ORG_EDITION, ISP_EDITION):
raise GeoIPError('Invalid database type; org_* methods expect '\
'Org/ISP database')
return self._get_org(ipnum)
except ValueError:
raise GeoIPError('*_by_addr methods only accept IP addresses. Use *_by_name for hostnames. (Address: %s)' % addr)
def org_by_name(self, hostname):
"""
Lookup the organization (or ISP) for hostname.
Use this method if you have an Organization/ISP database.
@param hostname: host name
@type hostname: str
@return: organization or ISP name
@rtype: str
"""
addr = socket.gethostbyname(hostname)
return self.org_by_addr(addr)
def record_by_addr(self, addr):
"""
Look up the record for a given IP address.
Use this method if you have a City database.
@param addr: IP address
@type addr: str
@return: dict with country_code, country_code3, country_name,
region, city, postal_code, latitude, longitude,
dma_code, metro_code, area_code, region_name, time_zone
@rtype: dict
"""
try:
ipnum = ip2long(addr)
if not ipnum:
raise ValueError("Invalid IP address: %s" % addr)
if not self._databaseType in (CITY_EDITION_REV0, CITY_EDITION_REV1):
raise GeoIPError('Invalid database type; record_* methods expect City database')
return self._get_record(ipnum)
except ValueError:
raise GeoIPError('*_by_addr methods only accept IP addresses. Use *_by_name for hostnames. (Address: %s)' % addr)
def record_by_name(self, hostname):
"""
Look up the record for a given hostname.
Use this method if you have a City database.
@param hostname: host name
@type hostname: str
@return: dict with country_code, country_code3, country_name,
region, city, postal_code, latitude, longitude,
dma_code, metro_code, area_code, region_name, time_zone
@rtype: dict
"""
addr = socket.gethostbyname(hostname)
return self.record_by_addr(addr)
def region_by_addr(self, addr):
"""
Lookup the region for given IP address.
Use this method if you have a Region database.
@param addr: IP address
@type addr: str
@return: dict containing country_code, region,
and region_name
@rtype: dict
"""
try:
ipnum = ip2long(addr)
if not ipnum:
raise ValueError("Invalid IP address: %s" % addr)
if not self._databaseType in (REGION_EDITION_REV0, REGION_EDITION_REV1,
CITY_EDITION_REV0, CITY_EDITION_REV1):
raise GeoIPError('Invalid database type; region_* methods expect '\
'Region or City database')
return self._get_region(ipnum)
except ValueError:
raise GeoIPError('*_by_addr methods only accept IP addresses. Use *_by_name for hostnames. (Address: %s)' % addr)
def region_by_name(self, hostname):
"""
Lookup the region for given hostname.
Use this method if you have a Region database.
@param hostname: host name
@type hostname: str
@return: dict containing country_code, region,
and region_name
@rtype: dict
"""
addr = socket.gethostbyname(hostname)
return self.region_by_addr(addr)

View File

@@ -1,382 +0,0 @@
"""
Constants needed for parsing binary GeoIP databases. It is part of the pygeoip
package.
@author: Jennifer Ennis <zaylea at gmail dot com>
@license:
Copyright(C) 2004 MaxMind LLC
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/lgpl.txt>.
"""
GEOIP_STANDARD = 0
GEOIP_MEMORY_CACHE = 1
DMA_MAP = {
500 : 'Portland-Auburn, ME',
501 : 'New York, NY',
502 : 'Binghamton, NY',
503 : 'Macon, GA',
504 : 'Philadelphia, PA',
505 : 'Detroit, MI',
506 : 'Boston, MA',
507 : 'Savannah, GA',
508 : 'Pittsburgh, PA',
509 : 'Ft Wayne, IN',
510 : 'Cleveland, OH',
511 : 'Washington, DC',
512 : 'Baltimore, MD',
513 : 'Flint, MI',
514 : 'Buffalo, NY',
515 : 'Cincinnati, OH',
516 : 'Erie, PA',
517 : 'Charlotte, NC',
518 : 'Greensboro, NC',
519 : 'Charleston, SC',
520 : 'Augusta, GA',
521 : 'Providence, RI',
522 : 'Columbus, GA',
523 : 'Burlington, VT',
524 : 'Atlanta, GA',
525 : 'Albany, GA',
526 : 'Utica-Rome, NY',
527 : 'Indianapolis, IN',
528 : 'Miami, FL',
529 : 'Louisville, KY',
530 : 'Tallahassee, FL',
531 : 'Tri-Cities, TN',
532 : 'Albany-Schenectady-Troy, NY',
533 : 'Hartford, CT',
534 : 'Orlando, FL',
535 : 'Columbus, OH',
536 : 'Youngstown-Warren, OH',
537 : 'Bangor, ME',
538 : 'Rochester, NY',
539 : 'Tampa, FL',
540 : 'Traverse City-Cadillac, MI',
541 : 'Lexington, KY',
542 : 'Dayton, OH',
543 : 'Springfield-Holyoke, MA',
544 : 'Norfolk-Portsmouth, VA',
545 : 'Greenville-New Bern-Washington, NC',
546 : 'Columbia, SC',
547 : 'Toledo, OH',
548 : 'West Palm Beach, FL',
549 : 'Watertown, NY',
550 : 'Wilmington, NC',
551 : 'Lansing, MI',
552 : 'Presque Isle, ME',
553 : 'Marquette, MI',
554 : 'Wheeling, WV',
555 : 'Syracuse, NY',
556 : 'Richmond-Petersburg, VA',
557 : 'Knoxville, TN',
558 : 'Lima, OH',
559 : 'Bluefield-Beckley-Oak Hill, WV',
560 : 'Raleigh-Durham, NC',
561 : 'Jacksonville, FL',
563 : 'Grand Rapids, MI',
564 : 'Charleston-Huntington, WV',
565 : 'Elmira, NY',
566 : 'Harrisburg-Lancaster-Lebanon-York, PA',
567 : 'Greenville-Spartenburg, SC',
569 : 'Harrisonburg, VA',
570 : 'Florence-Myrtle Beach, SC',
571 : 'Ft Myers, FL',
573 : 'Roanoke-Lynchburg, VA',
574 : 'Johnstown-Altoona, PA',
575 : 'Chattanooga, TN',
576 : 'Salisbury, MD',
577 : 'Wilkes Barre-Scranton, PA',
581 : 'Terre Haute, IN',
582 : 'Lafayette, IN',
583 : 'Alpena, MI',
584 : 'Charlottesville, VA',
588 : 'South Bend, IN',
592 : 'Gainesville, FL',
596 : 'Zanesville, OH',
597 : 'Parkersburg, WV',
598 : 'Clarksburg-Weston, WV',
600 : 'Corpus Christi, TX',
602 : 'Chicago, IL',
603 : 'Joplin-Pittsburg, MO',
604 : 'Columbia-Jefferson City, MO',
605 : 'Topeka, KS',
606 : 'Dothan, AL',
609 : 'St Louis, MO',
610 : 'Rockford, IL',
611 : 'Rochester-Mason City-Austin, MN',
612 : 'Shreveport, LA',
613 : 'Minneapolis-St Paul, MN',
616 : 'Kansas City, MO',
617 : 'Milwaukee, WI',
618 : 'Houston, TX',
619 : 'Springfield, MO',
620 : 'Tuscaloosa, AL',
622 : 'New Orleans, LA',
623 : 'Dallas-Fort Worth, TX',
624 : 'Sioux City, IA',
625 : 'Waco-Temple-Bryan, TX',
626 : 'Victoria, TX',
627 : 'Wichita Falls, TX',
628 : 'Monroe, LA',
630 : 'Birmingham, AL',
631 : 'Ottumwa-Kirksville, IA',
632 : 'Paducah, KY',
633 : 'Odessa-Midland, TX',
634 : 'Amarillo, TX',
635 : 'Austin, TX',
636 : 'Harlingen, TX',
637 : 'Cedar Rapids-Waterloo, IA',
638 : 'St Joseph, MO',
639 : 'Jackson, TN',
640 : 'Memphis, TN',
641 : 'San Antonio, TX',
642 : 'Lafayette, LA',
643 : 'Lake Charles, LA',
644 : 'Alexandria, LA',
646 : 'Anniston, AL',
647 : 'Greenwood-Greenville, MS',
648 : 'Champaign-Springfield-Decatur, IL',
649 : 'Evansville, IN',
650 : 'Oklahoma City, OK',
651 : 'Lubbock, TX',
652 : 'Omaha, NE',
656 : 'Panama City, FL',
657 : 'Sherman, TX',
658 : 'Green Bay-Appleton, WI',
659 : 'Nashville, TN',
661 : 'San Angelo, TX',
662 : 'Abilene-Sweetwater, TX',
669 : 'Madison, WI',
670 : 'Ft Smith-Fay-Springfield, AR',
671 : 'Tulsa, OK',
673 : 'Columbus-Tupelo-West Point, MS',
675 : 'Peoria-Bloomington, IL',
676 : 'Duluth, MN',
678 : 'Wichita, KS',
679 : 'Des Moines, IA',
682 : 'Davenport-Rock Island-Moline, IL',
686 : 'Mobile, AL',
687 : 'Minot-Bismarck-Dickinson, ND',
691 : 'Huntsville, AL',
692 : 'Beaumont-Port Author, TX',
693 : 'Little Rock-Pine Bluff, AR',
698 : 'Montgomery, AL',
702 : 'La Crosse-Eau Claire, WI',
705 : 'Wausau-Rhinelander, WI',
709 : 'Tyler-Longview, TX',
710 : 'Hattiesburg-Laurel, MS',
711 : 'Meridian, MS',
716 : 'Baton Rouge, LA',
717 : 'Quincy, IL',
718 : 'Jackson, MS',
722 : 'Lincoln-Hastings, NE',
724 : 'Fargo-Valley City, ND',
725 : 'Sioux Falls, SD',
734 : 'Jonesboro, AR',
736 : 'Bowling Green, KY',
737 : 'Mankato, MN',
740 : 'North Platte, NE',
743 : 'Anchorage, AK',
744 : 'Honolulu, HI',
745 : 'Fairbanks, AK',
746 : 'Biloxi-Gulfport, MS',
747 : 'Juneau, AK',
749 : 'Laredo, TX',
751 : 'Denver, CO',
752 : 'Colorado Springs, CO',
753 : 'Phoenix, AZ',
754 : 'Butte-Bozeman, MT',
755 : 'Great Falls, MT',
756 : 'Billings, MT',
757 : 'Boise, ID',
758 : 'Idaho Falls-Pocatello, ID',
759 : 'Cheyenne, WY',
760 : 'Twin Falls, ID',
762 : 'Missoula, MT',
764 : 'Rapid City, SD',
765 : 'El Paso, TX',
766 : 'Helena, MT',
767 : 'Casper-Riverton, WY',
770 : 'Salt Lake City, UT',
771 : 'Yuma, AZ',
773 : 'Grand Junction, CO',
789 : 'Tucson, AZ',
790 : 'Albuquerque, NM',
798 : 'Glendive, MT',
800 : 'Bakersfield, CA',
801 : 'Eugene, OR',
802 : 'Eureka, CA',
803 : 'Los Angeles, CA',
804 : 'Palm Springs, CA',
807 : 'San Francisco, CA',
810 : 'Yakima-Pasco, WA',
811 : 'Reno, NV',
813 : 'Medford-Klamath Falls, OR',
819 : 'Seattle-Tacoma, WA',
820 : 'Portland, OR',
821 : 'Bend, OR',
825 : 'San Diego, CA',
828 : 'Monterey-Salinas, CA',
839 : 'Las Vegas, NV',
855 : 'Santa Barbara, CA',
862 : 'Sacramento, CA',
866 : 'Fresno, CA',
868 : 'Chico-Redding, CA',
881 : 'Spokane, WA'
}
COUNTRY_CODES = (
'', 'AP', 'EU', 'AD', 'AE', 'AF', 'AG', 'AI', 'AL', 'AM', 'AN', 'AO', 'AQ',
'AR', 'AS', 'AT', 'AU', 'AW', 'AZ', 'BA', 'BB', 'BD', 'BE', 'BF', 'BG', 'BH',
'BI', 'BJ', 'BM', 'BN', 'BO', 'BR', 'BS', 'BT', 'BV', 'BW', 'BY', 'BZ', 'CA',
'CC', 'CD', 'CF', 'CG', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN', 'CO', 'CR', 'CU',
'CV', 'CX', 'CY', 'CZ', 'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG',
'EH', 'ER', 'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'FX', 'GA', 'GB',
'GD', 'GE', 'GF', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR', 'GS', 'GT',
'GU', 'GW', 'GY', 'HK', 'HM', 'HN', 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IN',
'IO', 'IQ', 'IR', 'IS', 'IT', 'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM',
'KN', 'KP', 'KR', 'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS',
'LT', 'LU', 'LV', 'LY', 'MA', 'MC', 'MD', 'MG', 'MH', 'MK', 'ML', 'MM', 'MN',
'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA',
'NC', 'NE', 'NF', 'NG', 'NI', 'NL', 'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA',
'PE', 'PF', 'PG', 'PH', 'PK', 'PL', 'PM', 'PN', 'PR', 'PS', 'PT', 'PW', 'PY',
'QA', 'RE', 'RO', 'RU', 'RW', 'SA', 'SB', 'SC', 'SD', 'SE', 'SG', 'SH', 'SI',
'SJ', 'SK', 'SL', 'SM', 'SN', 'SO', 'SR', 'ST', 'SV', 'SY', 'SZ', 'TC', 'TD',
'TF', 'TG', 'TH', 'TJ', 'TK', 'TM', 'TN', 'TO', 'TL', 'TR', 'TT', 'TV', 'TW',
'TZ', 'UA', 'UG', 'UM', 'US', 'UY', 'UZ', 'VA', 'VC', 'VE', 'VG', 'VI', 'VN',
'VU', 'WF', 'WS', 'YE', 'YT', 'RS', 'ZA', 'ZM', 'ME', 'ZW', 'A1', 'A2', 'O1',
'AX', 'GG', 'IM', 'JE', 'BL', 'MF'
)
COUNTRY_CODES3 = (
'','AP','EU','AND','ARE','AFG','ATG','AIA','ALB','ARM','ANT','AGO','AQ','ARG',
'ASM','AUT','AUS','ABW','AZE','BIH','BRB','BGD','BEL','BFA','BGR','BHR','BDI',
'BEN','BMU','BRN','BOL','BRA','BHS','BTN','BV','BWA','BLR','BLZ','CAN','CC',
'COD','CAF','COG','CHE','CIV','COK','CHL','CMR','CHN','COL','CRI','CUB','CPV',
'CX','CYP','CZE','DEU','DJI','DNK','DMA','DOM','DZA','ECU','EST','EGY','ESH',
'ERI','ESP','ETH','FIN','FJI','FLK','FSM','FRO','FRA','FX','GAB','GBR','GRD',
'GEO','GUF','GHA','GIB','GRL','GMB','GIN','GLP','GNQ','GRC','GS','GTM','GUM',
'GNB','GUY','HKG','HM','HND','HRV','HTI','HUN','IDN','IRL','ISR','IND','IO',
'IRQ','IRN','ISL','ITA','JAM','JOR','JPN','KEN','KGZ','KHM','KIR','COM','KNA',
'PRK','KOR','KWT','CYM','KAZ','LAO','LBN','LCA','LIE','LKA','LBR','LSO','LTU',
'LUX','LVA','LBY','MAR','MCO','MDA','MDG','MHL','MKD','MLI','MMR','MNG','MAC',
'MNP','MTQ','MRT','MSR','MLT','MUS','MDV','MWI','MEX','MYS','MOZ','NAM','NCL',
'NER','NFK','NGA','NIC','NLD','NOR','NPL','NRU','NIU','NZL','OMN','PAN','PER',
'PYF','PNG','PHL','PAK','POL','SPM','PCN','PRI','PSE','PRT','PLW','PRY','QAT',
'REU','ROU','RUS','RWA','SAU','SLB','SYC','SDN','SWE','SGP','SHN','SVN','SJM',
'SVK','SLE','SMR','SEN','SOM','SUR','STP','SLV','SYR','SWZ','TCA','TCD','TF',
'TGO','THA','TJK','TKL','TLS','TKM','TUN','TON','TUR','TTO','TUV','TWN','TZA',
'UKR','UGA','UM','USA','URY','UZB','VAT','VCT','VEN','VGB','VIR','VNM','VUT',
'WLF','WSM','YEM','YT','SRB','ZAF','ZMB','MNE','ZWE','A1','A2','O1',
'ALA','GGY','IMN','JEY','BLM','MAF'
)
COUNTRY_NAMES = (
"", "Asia/Pacific Region", "Europe", "Andorra", "United Arab Emirates",
"Afghanistan", "Antigua and Barbuda", "Anguilla", "Albania", "Armenia",
"Netherlands Antilles", "Angola", "Antarctica", "Argentina", "American Samoa",
"Austria", "Australia", "Aruba", "Azerbaijan", "Bosnia and Herzegovina",
"Barbados", "Bangladesh", "Belgium", "Burkina Faso", "Bulgaria", "Bahrain",
"Burundi", "Benin", "Bermuda", "Brunei Darussalam", "Bolivia", "Brazil",
"Bahamas", "Bhutan", "Bouvet Island", "Botswana", "Belarus", "Belize",
"Canada", "Cocos (Keeling) Islands", "Congo, The Democratic Republic of the",
"Central African Republic", "Congo", "Switzerland", "Cote D'Ivoire", "Cook Islands",
"Chile", "Cameroon", "China", "Colombia", "Costa Rica", "Cuba", "Cape Verde",
"Christmas Island", "Cyprus", "Czech Republic", "Germany", "Djibouti",
"Denmark", "Dominica", "Dominican Republic", "Algeria", "Ecuador", "Estonia",
"Egypt", "Western Sahara", "Eritrea", "Spain", "Ethiopia", "Finland", "Fiji",
"Falkland Islands (Malvinas)", "Micronesia, Federated States of", "Faroe Islands",
"France", "France, Metropolitan", "Gabon", "United Kingdom",
"Grenada", "Georgia", "French Guiana", "Ghana", "Gibraltar", "Greenland",
"Gambia", "Guinea", "Guadeloupe", "Equatorial Guinea", "Greece",
"South Georgia and the South Sandwich Islands",
"Guatemala", "Guam", "Guinea-Bissau",
"Guyana", "Hong Kong", "Heard Island and McDonald Islands", "Honduras",
"Croatia", "Haiti", "Hungary", "Indonesia", "Ireland", "Israel", "India",
"British Indian Ocean Territory", "Iraq", "Iran, Islamic Republic of",
"Iceland", "Italy", "Jamaica", "Jordan", "Japan", "Kenya", "Kyrgyzstan",
"Cambodia", "Kiribati", "Comoros", "Saint Kitts and Nevis",
"Korea, Democratic People's Republic of",
"Korea, Republic of", "Kuwait", "Cayman Islands",
"Kazakstan", "Lao People's Democratic Republic", "Lebanon", "Saint Lucia",
"Liechtenstein", "Sri Lanka", "Liberia", "Lesotho", "Lithuania", "Luxembourg",
"Latvia", "Libyan Arab Jamahiriya", "Morocco", "Monaco", "Moldova, Republic of",
"Madagascar", "Marshall Islands", "Macedonia",
"Mali", "Myanmar", "Mongolia", "Macau", "Northern Mariana Islands",
"Martinique", "Mauritania", "Montserrat", "Malta", "Mauritius", "Maldives",
"Malawi", "Mexico", "Malaysia", "Mozambique", "Namibia", "New Caledonia",
"Niger", "Norfolk Island", "Nigeria", "Nicaragua", "Netherlands", "Norway",
"Nepal", "Nauru", "Niue", "New Zealand", "Oman", "Panama", "Peru", "French Polynesia",
"Papua New Guinea", "Philippines", "Pakistan", "Poland", "Saint Pierre and Miquelon",
"Pitcairn Islands", "Puerto Rico", "Palestinian Territory",
"Portugal", "Palau", "Paraguay", "Qatar", "Reunion", "Romania",
"Russian Federation", "Rwanda", "Saudi Arabia", "Solomon Islands",
"Seychelles", "Sudan", "Sweden", "Singapore", "Saint Helena", "Slovenia",
"Svalbard and Jan Mayen", "Slovakia", "Sierra Leone", "San Marino", "Senegal",
"Somalia", "Suriname", "Sao Tome and Principe", "El Salvador", "Syrian Arab Republic",
"Swaziland", "Turks and Caicos Islands", "Chad", "French Southern Territories",
"Togo", "Thailand", "Tajikistan", "Tokelau", "Turkmenistan",
"Tunisia", "Tonga", "Timor-Leste", "Turkey", "Trinidad and Tobago", "Tuvalu",
"Taiwan", "Tanzania, United Republic of", "Ukraine",
"Uganda", "United States Minor Outlying Islands", "United States", "Uruguay",
"Uzbekistan", "Holy See (Vatican City State)", "Saint Vincent and the Grenadines",
"Venezuela", "Virgin Islands, British", "Virgin Islands, U.S.",
"Vietnam", "Vanuatu", "Wallis and Futuna", "Samoa", "Yemen", "Mayotte",
"Serbia", "South Africa", "Zambia", "Montenegro", "Zimbabwe",
"Anonymous Proxy","Satellite Provider","Other",
"Aland Islands","Guernsey","Isle of Man","Jersey","Saint Barthelemy","Saint Martin"
)
# storage / caching flags
STANDARD = 0
MEMORY_CACHE = 1
MMAP_CACHE = 8
# Database structure constants
COUNTRY_BEGIN = 16776960
STATE_BEGIN_REV0 = 16700000
STATE_BEGIN_REV1 = 16000000
STRUCTURE_INFO_MAX_SIZE = 20
DATABASE_INFO_MAX_SIZE = 100
# Database editions
COUNTRY_EDITION = 1
REGION_EDITION_REV0 = 7
REGION_EDITION_REV1 = 3
CITY_EDITION_REV0 = 6
CITY_EDITION_REV1 = 2
ORG_EDITION = 5
ISP_EDITION = 4
PROXY_EDITION = 8
ASNUM_EDITION = 9
NETSPEED_EDITION = 11
COUNTRY_EDITION_V6 = 12
SEGMENT_RECORD_LENGTH = 3
STANDARD_RECORD_LENGTH = 3
ORG_RECORD_LENGTH = 4
MAX_RECORD_LENGTH = 4
MAX_ORG_RECORD_LENGTH = 300
FULL_RECORD_LENGTH = 50
US_OFFSET = 1
CANADA_OFFSET = 677
WORLD_OFFSET = 1353
FIPS_RANGE = 360

View File

@@ -1,35 +0,0 @@
"""
Misc. utility functions. It is part of the pygeoip package.
@author: Jennifer Ennis <zaylea at gmail dot com>
@license:
Copyright(C) 2004 MaxMind LLC
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/lgpl.txt>.
"""
def ip2long(ip):
"""
Convert a IPv4 address into a 32-bit integer.
@param ip: quad-dotted IPv4 address
@type ip: str
@return: network byte order 32-bit integer
@rtype: int
"""
ip_array = ip.split('.')
ip_long = long(ip_array[0]) * 16777216 + long(ip_array[1]) * 65536 + long(ip_array[2]) * 256 + long(ip_array[3])
return ip_long

View File

@@ -1,19 +0,0 @@
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
#include <openssl/objects.h>
#include "sni_support.h"
void set_sni_ext(SSL *ctx, char *servername) {
SSL_set_tlsext_host_name(ctx, servername);
}
int get_nid(X509_EXTENSION *ext) {
return OBJ_obj2nid(X509_EXTENSION_get_object(ext));
}
binary_data_t get_unknown_value(X509_EXTENSION *ext) {
binary_data_t result;
result.size = ext->value->length;
result.data = (unsigned char *)ext->value->data;
return result;
}

View File

@@ -1,13 +0,0 @@
#ifndef SNI_SUPPORT_H
#define SNI_SUPPORT_H
typedef struct binary_data {
int size;
unsigned char* data;
} binary_data_t;
void set_sni_ext(SSL *ctx, char *servername);
int get_nid(X509_EXTENSION *ext);
binary_data_t get_unknown_value(X509_EXTENSION *ext);
#endif

View File

@@ -1,13 +0,0 @@
%module sni_support
%{
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
#include "sni_support.h"
%}
%typemap(out) binary_data_t {
$result = PyString_FromStringAndSize($1.data,$1.size);
}
%include "sni_support.h"

View File

@@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA4INAzUJea4/7KmYQWZJiKnxQ07IWASrgmmX3GwzY62W4WaEh
09p+vYO6yHFXzAa7Jelq4VxECsXzCfBzU72XNlTRup/kpy3zlgO4FzhhyuiOXqAb
m/ocLIXYSSI9nwI5YbJxNIPMNrd2vlaqy9O9km1rXyMzwTih1V1R82qP/xbarFBb
tE3SopmGrKv3jDrUy86AE4zjvzKBDobNMz4iV72B7xoecYq1I2grMsdoOUi0dUBc
bii7v8Ob4BKH0TWU5I6JmQkv59eS5JVL1mrnoACXgOdEfnO1F42S4qbjMyra+Gg/
A+2XnEq2Z7hakIr1BInqDqZ9E7ffb1vNNzttyQIDAQABAoIBAQCg1OXDJNxG6anf
waXMxNSV4uB0BotE4tZrRMg0FIwAdaoOayO0hD1uvcM8fBWNDvVaP1BViKcloJBu
elXjk1mTjkeADbLbIEvzF071M2q61MXIe6HLpuwv6OH74g+KrSk2F4KJQDE2sU7b
I3LO+SxAjSnxyWH19P/ZvTRLz/a/oOPH3YjCuSKZSLZdNwS9L5xJ8/ScYeawGFOx
7CEF1QIRiz14q/TXF93U4iAFtj7Awz1yepd2bQ9gYbJpiUK1kWa3rPcmJLL/tGpQ
HgONEMn4SxxrpX+/O+HnrusxWQhsgjt1iWUOjJIvvONqa5kiKXhjMURMm6BC2046
pwWy7VwBAoGBAPrRKH7ZeGPERv6TZILlkv3x/yKd/O0RP0JdGZyurkBzD+so61gc
qci+STOFlq5j+kqPnfYVhETsBsuMlwqKIHbJdinn9EHopy8wyuAHatQqHN2mWZ5V
EsGCfZILuIaYM1uTejqF/u2PRXDZdWPta9B7dhDXaXxvPSNXVAG1FlFhAoGBAOUm
8WsKrXIVVZvkmN3pWlXfJwhkn3+UG+yayYtque9smXDNETvg0QSUaIKuZj+blSLI
V2xtE6uleERpDjwiLVOgA/FlgZZxnVkZXUz4633I9pEuq1Zv5ZodSrQr0dg5R/Ap
B/V8ax2r1Y2Uosh/HmMhZirYck4EA/zc6jHshi1pAoGBAIm0+HTc1ZqBEzGGnzK2
9QN0ME5DS7ClPYQkNYGu7oD4K49DQiN4aUeMIgilmdtZjPwO6f1IRvzIUdrD79Gb
kMNnTPcpIRRmthPUyC5EJEUkcgDH6oBh2RBhoqviv4c2XUw0JnGnTBYGCWwyGJ+q
pP0sK+CHRKVLNdgHhFoDoKNBAoGAdOVg0lIoGMJ5YYVD1jBsPNIRf16VXueXNyPf
HJfdMh4cSEbUO8970PJEPiXpUxlzAsNglZcvKajHqV5OPK7SoI5IdKRrbuuWcRVX
WyTAPPJ/laBRF08NuYinyzFvYvYVlJXZ0Ykeu4wk7IyeXdk7DybCj6jK5rF1t1Ca
x8z4xcECgYEAvAvGL6b1fQkF1ihqVFqnQGpRGEef2POE0fnj13i+RAVWUm9k3b45
6losFlqxSNjpVxl5kFTGZcEh2UYqAB+fT7IwR/5IX6i+hXIv5U29Ng6BVNjvu2is
tijv/tSN07UvBZ4cFgZUw00aQ4hshCp95I0zYCwTtTIOgSRCJWQwUXY=
-----END RSA PRIVATE KEY-----

View File

@@ -1,120 +0,0 @@
import M2Crypto
from Crypto import Random
import sni_support
import hmac
import hashlib
import binascii
import socks
S_SIZE = 32
NONCE_SIZE = 32
def check(one, two, three, four, five):
print "done"
return 0
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()
#print "s: ", byteToHex(s)
#print "mac: ", byteToHex(mac)
#print "expected_mac: ", byteToHex(expected_mac)
if mac == expected_mac:
return True
return False
def verify_challenge(address, r, nonce, socksify=False):
"""
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()
context.set_allow_unknown_ca(True)
context.set_verify(M2Crypto.SSL.verify_none, 4)
#Consider placing try/catch block around wrong host exception
#or fix M2Crypto to handle SANs appropriately
M2Crypto.SSL.Connection.postConnectionCheck = None
conn = M2Crypto.SSL.Connection(context)
if socksify:
socksocket = socks.socksocket()
socksocket.setproxy(socks.PROXY_TYPE_SOCKS4, "localhost", 9050)
conn.socket = socksocket
sni_support.set_sni_ext(conn.ssl, sni_name)
peername = "[unknown]"
try:
conn.connect((address, 443))
peername = str(conn.socket.getpeername())
except Exception, e:
return False, "Connection to SSL Server failed (%s)" % str(e), peername
cert_chain = conn.get_peer_cert_chain()
#Ensure certificate chain form is correct
if cert_chain is None:
return False, "Client did not provide a certificate", peername
if len(cert_chain) != 1:
return False, "Chocolate client should only include 1 cert", peername
for i in range(0,cert_chain[0].get_ext_count()):
ext = cert_chain[0].get_ext_at(i)
# TODO: Pull out OID instead of nid
if sni_support.get_nid(ext.x509_ext) == 0:
valid = check_challenge_value(sni_support.get_unknown_value(ext.x509_ext), r)
if valid:
return True, "Challenge completed successfully", peername
else:
return False, "Certificate extension does not check out", peername
return False, "Chocolate extension not included in certificate", peername
def main():
#Testing the example sni_challenge
from Crypto.PublicKey import RSA
nonce = Random.get_random_bytes(NONCE_SIZE)
nonce = "nonce"
nonce2 = "nonce2"
r = Random.get_random_bytes(NONCE_SIZE)
r = "testValueForR"
r2 = "testValueForR2"
nonce = binascii.hexlify(nonce)
nonce2 = binascii.hexlify(nonce2)
valid, response, peername = verify_challenge("example.com", r, nonce)
#valid, response = verify_challenge("127.0.0.1", r, nonce)
print response
valid, response, peername = verify_challenge("www.example.com", r2, nonce2)
#valid, response = verify_challenge("localhost", r2, nonce2)
print response
if __name__ == "__main__":
main()

View File

@@ -1,25 +0,0 @@
#!/bin/sh
# By default, daemons are not being told to exit!
redis-cli del exit
echo "Starting logger daemon..."
nohup ./logging-daemon.py $@ &
# TODO: an attempt to reconstruct or expire sessions from previous
# runs of the daemon should occur here.
echo "Starting issue daemon..."
nohup ./issue-daemon.py $@ &
for instance in a b c
do
echo "Starting testchallenge daemon $instance..."
nohup ./testchallenge-daemon.py $@ &
done
echo "Starting makechallenge daemon..."
nohup ./makechallenge-daemon.py $@ &
echo "Starting payment daemon..."
nohup ./payment-daemon.py $@ &

View File

@@ -1,7 +0,0 @@
#!/bin/sh
redis-cli lpush exit exit
redis-cli publish exit clean-exit
# TODO: sleep a bit and then actually kill the daemon processes if they
# don't exit

View File

@@ -1,133 +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, time, sys, signal
import policy
from redis_lock import redis_lock
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, random, random_raw, log
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.
log("removing expired session", 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)
log("testing challenge %s" % short(challenge), session)
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
log("\tbeginning dvsni test to %s" % name, session)
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, direct_peername = verify_challenge(name, dvsni_r, dvsni_nonce, False)
proxy_result, proxy_reason, proxy_peername = verify_challenge(name, dvsni_r, dvsni_nonce, True)
log("\t* direct probe: %s (%s)" % (direct_result, direct_reason), session)
log("\t* probe was issued to %s" % direct_peername, session)
log("\t* Tor proxy probe: %s (%s)" % (proxy_result, proxy_reason), session)
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:
log("\tchallenge was not attempted", session)
all_satisfied = False
if all_satisfied:
# Challenges all succeeded, so we should prepare to issue
# the requested cert or request a payment if applicable.
# 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 policy.payment_required(session):
log("\t** All challenges satisfied; request NEEDS PAYMENT", session)
# Try to get a unique abbreviated ID (10 hex digits)
for i in xrange(20):
abbreviation = random()[:10]
if r.get("shorturl-%s" % abbreviation) is None:
break
else:
# Mysteriously unable to get a unique abbreviated session ID!
r.hset(session, "live", "False")
return
r.set("shorturl-%s" % abbreviation, session)
r.expire("shorturl-%s" % abbreviation, 3600)
r.hset(session, "shorturl", abbreviation)
r.hset(session, "state", "payment")
# According to current practice, there is no pending-payment
# queue because sessions can get out of payment state
# instantaneously as soon as the payment system sends a "payments"
# pubsub message to the payments daemon.
else:
log("\t** All challenges satisfied; request GRANTED", 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 "testchallenge daemon exiting cleanly"
break

View File

@@ -1,61 +0,0 @@
<!DOCTYPE html>
<html class="no-js">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Project Chocolate</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="/css/normalize.min.css">
<link rel="stylesheet" href="/css/main.css">
<!--[if lt IE 9]>
<script src="/js/vendor/html5.js"></script>
<script>window.html5 || document.write('<script src="/js/vendor/html5shiv.js"><\/script>')</script>
<![endif]-->
</head>
<body>
<div class="header-container">
<header class="wrapper clearfix">
<h1 class="title">Thank you</h1>
</header>
</div>
<div class="main-container">
<div class="main wrapper clearfix">
<article>
<header>
</header>
<section>
<h2>Thanks for your payment!</h2>
<p>
You successfully processed a credit card payment of <b>17.00 simoleons</b> for the
issuance of a digital certificate.</p><p>This payment relates to session ID %s, which
requested a certificate for the following names:</p>
</p>
%s
</p>
</section>
<footer>
</footer>
</article>
<aside>
<p>Following this successful payment, the requested certificate will be issued to you. Please tell your Trustify client application to continue at this time.</p>
</aside>
</div> <!-- #main -->
</div> <!-- #main-container -->
<div class="footer-container">
<footer class="wrapper">
</footer>
</div>
<script src="/js/main.js"></script>
</body>
</html>