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:
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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. 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. 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. 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. 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. Clients contain an HTTPS URL that always refers to a fresh client list. 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, <optional> [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. The server URL is an HTTPS endpoint that responds to the Trustify protocol as documented below. The policy strings are an optional list of parameters that may at some point express relevant comparison points between multiple CAs. 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. 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. 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. 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. 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. All failure messages are fatal and result in the server marking the session as expired, so that no further activity may occur. (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. 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. 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. (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. 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>, which are messages specifying tasks that must be completed to verify the preconditions of issuance for the certificate(s) that the client has requested. 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 challenges may be presented after previous sets, possibly as a result of information that the server obtained while verifying the earlier challenges.<br
|
||||
/><br
|
||||
/>Clients may decide to meet all of the outstanding challenges at once, or may decide to send responses to the challenges one at a time. The semantic 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 a matter of protocol synchronization, there are two subtypes of challenges: those where completion of the challenge is signaled by the client and then verified by the server (such as the DVSNI challenge 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 the DNS names for which they are requesting certificates. It is intended to be more automatable (and in some cases, more secure) than the email receipt verification that is commonly used by DV CAs, and 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 of the domain by not only changing responses from an HTTP server, but by 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 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> Shared parameters (chosen and sent by CA in the Challenge message):</i><br
|
||||
/> ${nonce} A randomly chosen nonce hex output of digest of (random 32-bytes)<br
|
||||
/> ${y} [:= E(r)] r is a random secret (32 bytes, raw binary)<br
|
||||
/> ${ext} x.509 extension (format? e.g: 1.3.3.7)<br
|
||||
/><br
|
||||
/><i> Client setup:</i><br
|
||||
/> Setup SNI for <i>${nonce}</i>.chocolate<br
|
||||
/> Serve a self-signed certificate with (critical(?)) X.509 extension <i>${ext}</i>,<br
|
||||
/> with value z = HMAC_r(s) || s (random s, r = D(${y}) (private key decrypt))<br
|
||||
/><br
|
||||
/><i> Chocolate Server (CA) verification steps:</i><br
|
||||
/> Connect to domain.com, with TLS SNI ${nonce}.chocolate<br
|
||||
/> Check certificate for X.509 extension ${ext}. Verify that value z is formed as expected.<br
|
||||
/> <br
|
||||
/> <i> Purpose:</i><br
|
||||
/> Proves that whoever has access to the corresponding private key also has the ability<br
|
||||
/> to serve (change configuration for) an arbitrary certificate for an arbitrary<br
|
||||
/> subdomain under the desired domain name.<br
|
||||
/><br
|
||||
/> <i>Explanation:</i><br
|
||||
/>Using ${nonce}.chocolate (instead of domain.com) allows a currently running web server to continue serving domain.com without interruption. (Note we still use the IP for domain.com when we connect to the server) It is of slight importance that the .chocolate TLD is not real; it may protect against elaborate attacks against domains like dyndns.com which would allow attackers to control nonce.dyndns.com. If such a domain were also hosted on a virtual hosting service/CDN with the same IP as the attacker, and which really did SNI, it would be important that .chocolate not be real.<br
|
||||
/>We require the certificate to be modified to ensure that it is freshly generated for this purpose, and not just being served a wildcard certificate. We could do this several ways, but it is necessary to tie this modification to control of the private key (hence the need for z) and to the current Chocolate request (hence the need for ${ext}). Otherwise, a "rogue CA" could receive a request from domain.com, forward it to a "real CA" with a different public key, and have the real domain.com carry out this challenge (which real CA would verify, and give rogue CA a signed cert for domain.com).<br
|
||||
/><br
|
||||
/> <i>Deviations from model:</i><br
|
||||
/> SNI only allows multiplexing over the domain. Apache always has a default SNI page... even if you provide a completely wrong header. The real challenge happens with y and z.<br
|
||||
/> Apache uses the first virtual server specified on the ip/port to act as a default server if the SNI extension is specified by the client but does 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. We expect that the hosting provider managing these guest domains should take its own steps to prevent one from listening on port 443 in an unconstrained manner for the other guests' domains. If it does not, the DVSNI verification step 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 other verification steps are applicable, may issue certificates to these other parties.<br
|
||||
/><br
|
||||
/>Verification of control on TLS ports other than 443 (such as the TLS email serivces 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 varied.<br
|
||||
/><br
|
||||
/><b>Proof-of-possession challenges:</b><br
|
||||
/><br
|
||||
/>The 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. 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. This challenge type requires 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 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. 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 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. 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. For example, the server SHOULD check that the RSA modulus of the submitted subject public key is >=2048 bits and that it is not on any weak RSA modulus blacklist. 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. 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. 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. 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. 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
|
||||
/> message SigningRequest {<br
|
||||
/> required int64 timestamp = 2;<br
|
||||
/> required string recipient = 3;<br
|
||||
/> required string csr = 4;<br
|
||||
/> required bytes sig = 5;<br
|
||||
/> optional string clientpuzzle = 6;<br
|
||||
/> }<br
|
||||
/><br
|
||||
/>timestamp is the Unix timestamp when the request was made. (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. <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
|
||||
/> message Failure {<br
|
||||
/> required FailureReason cause = 1;<br
|
||||
/> optional string URI = 2; /* for more human-readable information */<br
|
||||
/> }<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: <br
|
||||
/><br
|
||||
/> message Proceed {<br
|
||||
/> required int64 timestamp = 1;<br
|
||||
/> optional int32 polldelay = 2;<br
|
||||
/> }<br
|
||||
/><br
|
||||
/>timestamp is the Unix time when the message was issued. When sent by the server, polldelay is a suggested number of seconds to wait before contacting the server again.<br
|
||||
/><br
|
||||
/>Challenge:<br
|
||||
/><br
|
||||
/> message Challenge {<br
|
||||
/> required ChallengeType type = 1;<br
|
||||
/> optional string name = 2;<br
|
||||
/> repeated bytes data = 3;<br
|
||||
/> optional string URI = 4;<br
|
||||
/> optional bool succeeded = 5;<br
|
||||
/> }<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
|
||||
/> message Success {<br
|
||||
/> required string certificate = 1;<br
|
||||
/> optional string chain = 2;<br
|
||||
/> }<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
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -1 +0,0 @@
|
||||
These files only exist to help us test code as we're writing it.
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
1
attic/server-ca/.gitignore
vendored
1
attic/server-ca/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
SERVERNAME
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
@@ -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())
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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())
|
||||
@@ -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
1
attic/server-ca/demoCA/.gitignore
vendored
1
attic/server-ca/demoCA/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
newcerts
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
unique_subject = no
|
||||
@@ -1 +0,0 @@
|
||||
unique_subject = yes
|
||||
@@ -1 +0,0 @@
|
||||
V 150707200759Z 93A580A1E0D9B5C2 unknown /C=US/ST=CA/O=Internet Widgits Pty Ltd/CN=notreally.eff.org/emailAddress=notreally@eff.org
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -1 +0,0 @@
|
||||
93A580A1E0D9B5C4
|
||||
@@ -1 +0,0 @@
|
||||
93A580A1E0D9B5C3
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
5
attic/server-ca/sni_challenge/.gitignore
vendored
5
attic/server-ca/sni_challenge/.gitignore
vendored
@@ -1,5 +0,0 @@
|
||||
_sni_support.so
|
||||
sni_support_wrap.c
|
||||
sni_support.py
|
||||
sni_support.o
|
||||
sni_support_wrap.o
|
||||
@@ -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
|
||||
@@ -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().
|
||||
@@ -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))
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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-----
|
||||
@@ -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()
|
||||
@@ -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 $@ &
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user