Merge remote-tracking branch 'upstream/master' into revoker
This commit is contained in:
commit
461f03bb11
1
test.sh
1
test.sh
|
|
@ -63,7 +63,6 @@ else
|
|||
run go test ${dirlist}
|
||||
fi
|
||||
|
||||
echo "Checking for unformatted files:"
|
||||
unformatted=$(find . -name "*.go" -not -path "./Godeps/*" -print | xargs -n1 gofmt -l)
|
||||
if [ "x${unformatted}" != "x" ] ; then
|
||||
echo "Unformatted files found; setting failure state."
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"syslog": {
|
||||
"network": "",
|
||||
"server": "",
|
||||
"tag": "boulder"
|
||||
},
|
||||
|
||||
"amqp": {
|
||||
"server": "amqp://guest:guest@localhost:5672",
|
||||
"RA": {
|
||||
"client": "RA.client",
|
||||
"server": "RA.server"
|
||||
},
|
||||
"VA": {
|
||||
"client": "VA.client",
|
||||
"server": "VA.server"
|
||||
},
|
||||
"SA": {
|
||||
"client": "SA.client",
|
||||
"server": "SA.server"
|
||||
},
|
||||
"CA": {
|
||||
"client": "CA.client",
|
||||
"server": "CA.server"
|
||||
}
|
||||
},
|
||||
|
||||
"statsd": {
|
||||
"server": "localhost:8125",
|
||||
"prefix": "Boulder"
|
||||
},
|
||||
|
||||
"wfe": {
|
||||
"baseURL": "http://localhost:4300",
|
||||
"listenAddress": "0.0.0.0:4300"
|
||||
},
|
||||
|
||||
"ca": {
|
||||
"server": "localhost:9300",
|
||||
"authKey": "79999d86250c367a2b517a1ae7d409c1",
|
||||
"serialPrefix": 255,
|
||||
"profile": "ee",
|
||||
"dbDriver": "sqlite3",
|
||||
"dbName": ":memory:",
|
||||
"testMode": true,
|
||||
"issuerCert": "test/test-ca.pem",
|
||||
"_comment": "This should only be present in testMode. In prod use an HSM.",
|
||||
"issuerKey": "test/test-ca.key"
|
||||
},
|
||||
|
||||
"sa": {
|
||||
"dbDriver": "sqlite3",
|
||||
"dbName": ":memory:"
|
||||
},
|
||||
|
||||
"mail": {
|
||||
"server": "mail.example.com",
|
||||
"port": "25",
|
||||
"username": "cert-master@example.com",
|
||||
"password": "password"
|
||||
},
|
||||
|
||||
"subscriberAgreementURL": "http://localhost:4300/terms"
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
#!/bin/bash
|
||||
cd $(dirname $0)/..
|
||||
|
||||
# Ensure cleanup
|
||||
trap "trap '' SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
|
||||
|
||||
go run ./cmd/boulder/main.go --config test/boulder-test-config.json &>/dev/null &
|
||||
go run Godeps/_workspace/src/github.com/cloudflare/cfssl/cmd/cfssl/cfssl.go \
|
||||
-loglevel 0 \
|
||||
serve \
|
||||
-port 9300 \
|
||||
-ca test/test-ca.pem \
|
||||
-ca-key test/test-ca.key \
|
||||
-config test/cfssl-config.json &>/dev/null &
|
||||
|
||||
cd test/js
|
||||
npm install
|
||||
|
||||
# Wait for Boulder to come up
|
||||
until nc localhost 4300 < /dev/null ; do sleep 1 ; done
|
||||
|
||||
CERT_KEY=$(mktemp /tmp/cert_XXXXX.pem)
|
||||
CERT=$(mktemp /tmp/cert_XXXXX.crt)
|
||||
|
||||
node test.js --email foo@bar.com --agree true \
|
||||
--domain foo.com --new-reg http://localhost:4300/acme/new-reg \
|
||||
--certKey ${CERT_KEY} --cert ${CERT} && \
|
||||
node revoke.js ${CERT} ${CERT_KEY} http://localhost:4300/acme/revoke-cert/
|
||||
|
||||
STATUS=$?
|
||||
|
||||
# Cleanup
|
||||
rm -f ${CERT_KEY}
|
||||
rm -f ${CERT}
|
||||
rm -f account-key.pem
|
||||
rm -f temp-cert.pem
|
||||
|
||||
exit $STATUS
|
||||
|
|
@ -24,7 +24,4 @@ The node.js scripts in this directory provide a simple end-to-end test of Boulde
|
|||
|
||||
# Client side
|
||||
|
||||
mkdir -p .well-known/acme-challenge/
|
||||
node test.js
|
||||
mv -- *.txt .well-known/acme-challenge/ # In a different window
|
||||
python -m SimpleHTTPServer 5001 # In yet another window
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"cli": "^0.6.5",
|
||||
"colors": "^1.1.0",
|
||||
"inquirer": "^0.8.2",
|
||||
"node-forge": "^0.6.21",
|
||||
"request": "^2.55.0"
|
||||
|
|
|
|||
|
|
@ -15,12 +15,13 @@ var fs = require('fs');
|
|||
var request = require('request');
|
||||
|
||||
function main() {
|
||||
if (process.argv.length != 4) {
|
||||
console.log('Usage: js revoke.js cert.der key.pem');
|
||||
if (process.argv.length != 5) {
|
||||
console.log('Usage: js revoke.js cert.der key.pem REVOKE_URL');
|
||||
process.exit(1);
|
||||
}
|
||||
var key = crypto.importPemPrivateKey(fs.readFileSync(process.argv[3]));
|
||||
var certDER = fs.readFileSync(process.argv[2])
|
||||
var revokeUrl = process.argv[4];
|
||||
var certDERB64URL = util.b64enc(new Buffer(certDER))
|
||||
var revokeMessage = JSON.stringify({
|
||||
// For some reason forge adds an extra, incorrect '00' at the front of the
|
||||
|
|
@ -29,7 +30,7 @@ function main() {
|
|||
});
|
||||
console.log('Requesting revocation:', revokeMessage)
|
||||
var jws = crypto.generateSignature(key, new Buffer(revokeMessage));
|
||||
var req = request.post('http://localhost:4000/acme/revoke-cert/', function(err, resp) {
|
||||
var req = request.post(revokeUrl, function(err, resp) {
|
||||
if (err) {
|
||||
console.log('Error: ', err);
|
||||
}
|
||||
|
|
|
|||
124
test/js/test.js
124
test/js/test.js
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
var colors = require("colors");
|
||||
var cli = require("cli");
|
||||
var crypto = require("./crypto-util");
|
||||
var child_process = require('child_process');
|
||||
|
|
@ -80,19 +81,30 @@ var questions = {
|
|||
}],
|
||||
};
|
||||
|
||||
var cliOptions = cli.parse({
|
||||
// To test against the demo instance, pass --newReg "https://www.letsencrypt-demo.org/acme/new-reg"
|
||||
// To get a cert from the demo instance, you must be publicly reachable on
|
||||
// port 443 under the DNS name you are trying to get, and run test.js as root.
|
||||
newReg: ["new-reg", "New Registration URL", "string", "http://localhost:4000/acme/new-reg"],
|
||||
certKeyFile: ["certKey", "File for cert key (created if not exists)", "path", "cert-key.pem"],
|
||||
certFile: ["cert", "Path to output certificate (DER format)", "path", "cert.pem"],
|
||||
email: ["email", "Email address", "string", null],
|
||||
agreeTerms: ["agree", "Agree to terms of service", "boolean", null],
|
||||
domain: ["domain", "Domain name for which to request a certificate", "string", null],
|
||||
});
|
||||
|
||||
var state = {
|
||||
certPrivateKey: null,
|
||||
accountPrivateKey: null,
|
||||
|
||||
//newRegistrationURL: "https://www.letsencrypt-demo.org/acme/new-reg",
|
||||
newRegistrationURL: "http://localhost:4000/acme/new-reg",
|
||||
newRegistrationURL: cliOptions.newReg,
|
||||
registrationURL: "",
|
||||
|
||||
termsRequired: false,
|
||||
termsAgreed: false,
|
||||
termsAgreed: null,
|
||||
termsURL: null,
|
||||
|
||||
domain: null,
|
||||
domain: cliOptions.domain,
|
||||
|
||||
newAuthorizationURL: "",
|
||||
authorizationURL: "",
|
||||
|
|
@ -102,8 +114,8 @@ var state = {
|
|||
|
||||
newCertificateURL: "",
|
||||
certificateURL: "",
|
||||
certFile: "",
|
||||
keyFile: ""
|
||||
certFile: cliOptions.certFile,
|
||||
keyFile: cliOptions.certKeyFile,
|
||||
};
|
||||
|
||||
function parseLink(link) {
|
||||
|
|
@ -136,11 +148,32 @@ function post(url, body, callback) {
|
|||
var jws = crypto.generateSignature(state.accountPrivateKey, new Buffer(payload));
|
||||
var signed = JSON.stringify(jws, null, 2);
|
||||
|
||||
var req = request.post(url, callback);
|
||||
console.log('Posting to', url, ':\n', signed);
|
||||
console.log('Payload:', payload);
|
||||
console.log('Posting to', url, ':');
|
||||
console.log(signed.green);
|
||||
console.log('Payload:')
|
||||
console.log(payload.blue);
|
||||
var req = request.post({
|
||||
url: url,
|
||||
encoding: null // Return body as buffer, needed for certificate response
|
||||
}, function(error, response, body) {
|
||||
// Don't print non-ASCII characters (like DER-encoded cert) to the terminal
|
||||
if (body && !body.toString().match(/[^\x00-\x7F]/)) {
|
||||
try {
|
||||
var parsed = JSON.parse(body);
|
||||
console.log(JSON.stringify(parsed, null, 2).cyan);
|
||||
} catch (e) {
|
||||
console.log(body.toString().cyan);
|
||||
}
|
||||
}
|
||||
callback(error, response, body)
|
||||
});
|
||||
req.on('response', function(response) {
|
||||
console.log(response.headers)
|
||||
Object.keys(response.headers).forEach(function(key) {
|
||||
var value = response.headers[key];
|
||||
var upcased = key.charAt(0).toUpperCase() + key.slice(1);
|
||||
console.log((upcased + ": " + value).yellow)
|
||||
});
|
||||
console.log()
|
||||
})
|
||||
req.write(signed)
|
||||
req.end();
|
||||
|
|
@ -183,12 +216,10 @@ saveFiles
|
|||
*/
|
||||
|
||||
function main() {
|
||||
inquirer.prompt(questions.files, makeKeyPair);
|
||||
makeKeyPair();
|
||||
}
|
||||
|
||||
function makeKeyPair(answers) {
|
||||
state.certFile = answers.certFile;
|
||||
state.keyFile = answers.keyFile;
|
||||
function makeKeyPair() {
|
||||
console.log("Generating cert key pair...");
|
||||
child_process.exec("openssl req -newkey rsa:2048 -keyout " + state.keyFile + " -days 3650 -subj /CN=foo -nodes -x509 -out temp-cert.pem", function (error, stdout, stderr) {
|
||||
if (error) {
|
||||
|
|
@ -198,7 +229,7 @@ function makeKeyPair(answers) {
|
|||
state.certPrivateKey = crypto.importPemPrivateKey(fs.readFileSync(state.keyFile));
|
||||
|
||||
console.log();
|
||||
makeAccountKeyPair(answers)
|
||||
makeAccountKeyPair()
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -212,7 +243,11 @@ function makeAccountKeyPair(answers) {
|
|||
state.accountPrivateKey = crypto.importPemPrivateKey(fs.readFileSync("account-key.pem"));
|
||||
|
||||
console.log();
|
||||
inquirer.prompt(questions.email, register)
|
||||
if (cliOptions.email) {
|
||||
register({email: cliOptions.email});
|
||||
} else {
|
||||
inquirer.prompt(questions.email, register)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -229,13 +264,13 @@ function getTerms(err, resp) {
|
|||
if (err || Math.floor(resp.statusCode / 100) != 2) {
|
||||
// Non-2XX response
|
||||
console.log("Registration request failed:" + err);
|
||||
return;
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var links = parseLink(resp.headers["link"]);
|
||||
if (!links || !("next" in links)) {
|
||||
console.log("The server did not provide information to proceed");
|
||||
return
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
state.registrationURL = resp.headers["location"];
|
||||
|
|
@ -262,7 +297,11 @@ function getAgreement(err, resp, body) {
|
|||
console.log(body);
|
||||
console.log();
|
||||
|
||||
inquirer.prompt(questions.terms, sendAgreement);
|
||||
if (!cliOptions.agreeTerms) {
|
||||
inquirer.prompt(questions.terms, sendAgreement);
|
||||
} else {
|
||||
sendAgreement({terms: true});
|
||||
}
|
||||
}
|
||||
|
||||
function sendAgreement(answers) {
|
||||
|
|
@ -286,7 +325,11 @@ function sendAgreement(answers) {
|
|||
console.log("Couldn't POST agreement back to server, aborting.");
|
||||
process.exit(1);
|
||||
} else {
|
||||
inquirer.prompt(questions.domain, getChallenges);
|
||||
if (!state.domain) {
|
||||
inquirer.prompt(questions.domain, getChallenges);
|
||||
} else {
|
||||
getChallenges({domain: state.domain});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -307,13 +350,13 @@ function getReadyToValidate(err, resp, body) {
|
|||
if (err || Math.floor(resp.statusCode / 100) != 2) {
|
||||
// Non-2XX response
|
||||
console.log("Authorization request failed with code " + resp.statusCode)
|
||||
return;
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var links = parseLink(resp.headers["link"]);
|
||||
if (!links || !("next" in links)) {
|
||||
console.log("The server did not provide information to proceed");
|
||||
return
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
state.authorizationURL = resp.headers["location"];
|
||||
|
|
@ -324,19 +367,18 @@ function getReadyToValidate(err, resp, body) {
|
|||
var simpleHttps = authz.challenges.filter(function(x) { return x.type == "simpleHttps"; });
|
||||
if (simpleHttps.length == 0) {
|
||||
console.log("The server didn't offer any challenges we can handle.");
|
||||
return;
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var challenge = simpleHttps[0];
|
||||
var path = crypto.randomString(8) + ".txt";
|
||||
var challengePath = ".well-known/acme-challenge/" + path;
|
||||
fs.writeFileSync(challengePath, challenge.token);
|
||||
state.responseURL = challenge["uri"];
|
||||
state.path = path;
|
||||
|
||||
// For local, test-mode validation
|
||||
function httpResponder(req, response) {
|
||||
console.log("Got request for", req.url);
|
||||
console.log("\nGot request for", req.url);
|
||||
var host = req.headers["host"];
|
||||
if ((host === state.domain || /localhost/.test(state.newRegistrationURL)) &&
|
||||
req.method === "GET" &&
|
||||
|
|
@ -348,7 +390,7 @@ function getReadyToValidate(err, resp, body) {
|
|||
response.writeHead(404, {"Content-Type": "text/plain"});
|
||||
response.end("");
|
||||
}
|
||||
};
|
||||
}
|
||||
if (/localhost/.test(state.newRegistrationURL)) {
|
||||
var httpServer = http.createServer(httpResponder)
|
||||
httpServer.listen(5001)
|
||||
|
|
@ -360,10 +402,6 @@ function getReadyToValidate(err, resp, body) {
|
|||
httpServer.listen(443)
|
||||
}
|
||||
|
||||
inquirer.prompt(questions.readyToValidate, sendResponse);
|
||||
}
|
||||
|
||||
function sendResponse() {
|
||||
cli.spinner("Validating domain");
|
||||
post(state.responseURL, {
|
||||
path: state.path
|
||||
|
|
@ -374,7 +412,7 @@ function ensureValidation(err, resp, body) {
|
|||
if (Math.floor(resp.statusCode / 100) != 2) {
|
||||
// Non-2XX response
|
||||
console.log("Authorization status request failed with code " + resp.statusCode)
|
||||
return;
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var authz = JSON.parse(body);
|
||||
|
|
@ -389,35 +427,21 @@ function ensureValidation(err, resp, body) {
|
|||
getCertificate();
|
||||
} else if (authz.status == "invalid") {
|
||||
console.log("The CA was unable to validate the file you provisioned:" + body);
|
||||
return;
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log("The CA returned an authorization in an unexpected state");
|
||||
console.log(JSON.stringify(authz, null, " "));
|
||||
return;
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function getCertificate() {
|
||||
cli.spinner("Requesting certificate");
|
||||
var csr = crypto.generateCSR(state.certPrivateKey, state.domain);
|
||||
|
||||
var certificateMessage = JSON.stringify({
|
||||
post(state.newCertificateURL, {
|
||||
csr: csr,
|
||||
authorizations: [ state.authorizationURL ]
|
||||
});
|
||||
var jws = crypto.generateSignature(state.accountPrivateKey, new Buffer(certificateMessage));
|
||||
var payload = JSON.stringify(jws);
|
||||
|
||||
cli.spinner("Requesting certificate");
|
||||
|
||||
var url = state.newCertificateURL;
|
||||
console.log('Posting to', url, ':\n', payload);
|
||||
|
||||
var req = request.post({
|
||||
url: url,
|
||||
encoding: null // Return body as buffer.
|
||||
}, downloadCertificate);
|
||||
req.write(payload)
|
||||
req.end();
|
||||
}
|
||||
|
||||
function downloadCertificate(err, resp, body) {
|
||||
|
|
@ -425,7 +449,7 @@ function downloadCertificate(err, resp, body) {
|
|||
// Non-2XX response
|
||||
console.log("Certificate request failed with code " + resp.statusCode);
|
||||
console.log(body.toString());
|
||||
return;
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
cli.spinner("Requesting certificate ... done", true);
|
||||
|
|
|
|||
|
|
@ -275,7 +275,10 @@ func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, reques
|
|||
return
|
||||
}
|
||||
|
||||
regURL := fmt.Sprintf("%s%s", wfe.RegBase, string(reg.ID))
|
||||
// Use an explicitly typed variable. Otherwise `go vet' incorrectly complains
|
||||
// that reg.ID is a string being passed to %d.
|
||||
var id int64 = reg.ID
|
||||
regURL := fmt.Sprintf("%s%d", wfe.RegBase, id)
|
||||
responseBody, err := json.Marshal(reg)
|
||||
if err != nil {
|
||||
wfe.sendError(response, "Error marshaling authz", err, http.StatusInternalServerError)
|
||||
|
|
@ -285,8 +288,8 @@ func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, reques
|
|||
response.Header().Add("Location", regURL)
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
response.Header().Add("Link", link(wfe.NewAuthz, "next"))
|
||||
if len(wfe.TermsPath) > 0 {
|
||||
response.Header().Add("Link", link(wfe.BaseURL+wfe.TermsPath, "terms-of-service"))
|
||||
if len(wfe.SubscriberAgreementURL) > 0 {
|
||||
response.Header().Add("Link", link(wfe.SubscriberAgreementURL, "terms-of-service"))
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusCreated)
|
||||
|
|
@ -357,15 +360,13 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
|
|||
return
|
||||
}
|
||||
|
||||
body, requestKey, reg, err := wfe.verifyPOST(request, false)
|
||||
// We don't ask verifyPOST to verify there is a correponding registration,
|
||||
// because anyone with the right private key can revoke a certificate.
|
||||
body, requestKey, _, err := wfe.verifyPOST(request, false)
|
||||
if err != nil {
|
||||
wfe.sendError(response, "Unable to read/verify body", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if reg.Agreement == "" {
|
||||
wfe.sendError(response, "Must agree to subscriber agreement before any further actions", nil, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
type RevokeRequest struct {
|
||||
CertificateDER core.JsonBuffer `json:"certificate"`
|
||||
|
|
@ -618,7 +619,9 @@ func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *
|
|||
}
|
||||
|
||||
if len(update.Agreement) > 0 && update.Agreement != wfe.SubscriberAgreementURL {
|
||||
wfe.sendError(response, fmt.Sprintf("Provided agreement URL [%s] does not match current agreement URL [%s]", update.Agreement, wfe.SubscriberAgreementURL), nil, http.StatusBadRequest)
|
||||
wfe.sendError(response,
|
||||
fmt.Sprintf("Provided agreement URL [%s] does not match current agreement URL [%s]",
|
||||
update.Agreement, wfe.SubscriberAgreementURL), nil, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue