Initial anti-replay mechanism

This commit is contained in:
Richard Barnes 2015-06-08 15:02:39 -04:00
parent cf0b2e9cc2
commit a620fe4583
5 changed files with 148 additions and 19 deletions

View File

@ -157,10 +157,10 @@ func (ctx *genericSigner) Sign(payload []byte) (*JsonWebSignature, error) {
}
// Verify validates the signature on the object and returns the payload.
func (obj JsonWebSignature) Verify(verificationKey interface{}) ([]byte, error) {
func (obj JsonWebSignature) Verify(verificationKey interface{}) ([]byte, []byte, error) {
verifier, err := newVerifier(verificationKey)
if err != nil {
return nil, err
return nil, nil, err
}
for _, signature := range obj.Signatures {
@ -174,9 +174,9 @@ func (obj JsonWebSignature) Verify(verificationKey interface{}) ([]byte, error)
alg := SignatureAlgorithm(headers.Alg)
err := verifier.verifyPayload(input, signature.signature, alg)
if err == nil {
return obj.payload, nil
return obj.payload, signature.original.Protected.data, nil
}
}
return nil, ErrCryptoFailure
return nil, nil, ErrCryptoFailure
}

View File

@ -7,4 +7,4 @@ fi
# Kill all children on exit.
export BOULDER_CONFIG=${BOULDER_CONFIG:-test/boulder-config.json}
exec go run ./cmd/boulder/main.go
exec go run -tags pkcs11 ./cmd/boulder/main.go

View File

@ -139,13 +139,16 @@ module.exports = {
///// SIGNATURE GENERATION / VERIFICATION
generateSignature: function(keyPair, payload) {
var nonce = bytesToBuffer(forge.random.getBytesSync(NONCE_SIZE));
generateSignature: function(keyPair, payload, nonceIn) {
var nonce = nonceIn
if (!nonce) {
nonce = util.b64enc(bytesToBuffer(forge.random.getBytesSync(NONCE_SIZE)));
}
var privateKey = importPrivateKey(keyPair.privateKey);
// Compute JWS signature
var protectedHeader = JSON.stringify({
nonce: util.b64enc(nonce)
nonce: nonce
});
var protected64 = util.b64enc(new Buffer(protectedHeader));
var payload64 = util.b64enc(payload);

View File

@ -116,6 +116,9 @@ var state = {
validatedDomains: [],
validAuthorizationURLs: [],
// We will use this as a push/shift FIFO in post() and getNonce()
nonces: [],
newAuthorizationURL: "",
authorizationURL: "",
responseURL: "",
@ -153,9 +156,36 @@ function parseLink(link) {
}
}
function getNonce(url, callback) {
var req = request.head({
url: url,
strictSSL: false
}, function(error, response, body) {
if ("replay-nonce" in response.headers) {
console.log("Storing nonce: " + response.headers["replay-nonce"]);
state.nonces.push(response.headers["replay-nonce"]);
callback();
return;
}
console.log("Failed to get nonce for request")
});
}
function post(url, body, callback) {
// Pre-flight with HEAD if we don't have a nonce
if (state.nonces.length == 0) {
getNonce(url, function() {
post(url, body, callback);
})
return;
}
console.log("Using nonce: " + state.nonces[0]);
var payload = JSON.stringify(body, null, 2);
var jws = crypto.generateSignature(state.accountPrivateKey, new Buffer(payload));
var jws = crypto.generateSignature(state.accountPrivateKey,
new Buffer(payload),
state.nonces.shift());
var signed = JSON.stringify(jws, null, 2);
console.log('Posting to', url, ':');
@ -164,8 +194,16 @@ function post(url, body, callback) {
console.log(payload.blue);
var req = request.post({
url: url,
encoding: null // Return body as buffer, needed for certificate response
}, function(error, response, body) {
encoding: null, // Return body as buffer, needed for certificate response
strictSSL: false,
}, function(error, response, body) {
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()
// Don't print non-ASCII characters (like DER-encoded cert) to the terminal
if (body && !body.toString().match(/[^\x00-\x7F]/)) {
try {
@ -175,15 +213,16 @@ function post(url, body, callback) {
console.log(body.toString().cyan);
}
}
// Remember the nonce provided by the server
if ("replay-nonce" in response.headers) {
console.log("Storing nonce: " + response.headers["replay-nonce"]);
state.nonces.push(response.headers["replay-nonce"]);
}
callback(error, response, body)
});
req.on('response', function(response) {
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();

View File

@ -60,6 +60,9 @@ type WebFrontEndImpl struct {
// URL to the current subscriber agreement (should contain some version identifier)
SubscriberAgreementURL string
// Register of anti-replay nonces
replayNonces map[string]bool
}
func statusCodeFromError(err interface{}) int {
@ -79,7 +82,8 @@ func NewWebFrontEndImpl() WebFrontEndImpl {
logger := blog.GetAuditLogger()
logger.Notice("Web Front End Starting")
return WebFrontEndImpl{
log: logger,
log: logger,
replayNonces: map[string]bool{},
}
}
@ -149,6 +153,18 @@ const (
ServerInternalProblem = ProblemType("urn:acme:error:serverInternal")
)
func (wfe *WebFrontEndImpl) sendNonce(response http.ResponseWriter) (err error) {
nonce := core.NewToken()
if wfe.replayNonces[nonce] {
wfe.log.Debug("Nonce collision detected")
return errors.New("Nonce collision detected")
}
wfe.replayNonces[nonce] = true
response.Header().Set("Replay-Nonce", nonce)
return
}
func (wfe *WebFrontEndImpl) verifyPOST(request *http.Request, regCheck bool) ([]byte, *jose.JsonWebKey, core.Registration, error) {
var reg core.Registration
@ -184,13 +200,29 @@ func (wfe *WebFrontEndImpl) verifyPOST(request *http.Request, regCheck bool) ([]
return nil, nil, reg, errors.New("POST not signed")
}
key := parsedJws.Signatures[0].Header.JsonWebKey
payload, err := parsedJws.Verify(key)
payload, header, err := parsedJws.Verify(key)
if err != nil {
wfe.log.Debug(string(body))
wfe.log.Debug(fmt.Sprintf("JWS verification error: %v", err))
return nil, nil, reg, err
}
// Check that the request has a known anti-replay nonce
// i.e., Nonce is in protected header and
var protected struct {
Nonce string `json:"nonce"`
}
err = json.Unmarshal(header, &protected)
if err != nil || len(protected.Nonce) == 0 {
wfe.log.Debug("JWS has no anti-replay nonce")
return nil, nil, reg, errors.New("JWS has no anti-replay nonce")
} else if !wfe.replayNonces[protected.Nonce] {
wfe.log.Debug(fmt.Sprintf("JWS has invalid anti-replay nonce: %s"))
return nil, nil, reg, errors.New("JWS has invalid anti-replay nonce")
} else {
delete(wfe.replayNonces, protected.Nonce)
}
if regCheck {
// Check that the key is assosiated with an actual account
reg, err = wfe.SA.GetRegistrationByKey(*key)
@ -246,6 +278,11 @@ func link(url, relation string) string {
}
func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, request *http.Request) {
err := wfe.sendNonce(response)
if err != nil {
wfe.sendError(response, "Anti-replay nonce collision", err, http.StatusInternalServerError)
}
if request.Method != "POST" {
wfe.sendError(response, "Method not allowed", "", http.StatusMethodNotAllowed)
return
@ -305,6 +342,11 @@ func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, reques
}
func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, request *http.Request) {
err := wfe.sendNonce(response)
if err != nil {
wfe.sendError(response, "Anti-replay nonce collision", err, http.StatusInternalServerError)
}
if request.Method != "POST" {
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
return
@ -362,6 +404,11 @@ func (wfe *WebFrontEndImpl) NewAuthorization(response http.ResponseWriter, reque
}
func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, request *http.Request) {
err := wfe.sendNonce(response)
if err != nil {
wfe.sendError(response, "Anti-replay nonce collision", err, http.StatusInternalServerError)
}
if request.Method != "POST" {
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
return
@ -437,6 +484,11 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ
}
func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request *http.Request) {
err := wfe.sendNonce(response)
if err != nil {
wfe.sendError(response, "Anti-replay nonce collision", err, http.StatusInternalServerError)
}
if request.Method != "POST" {
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
return
@ -508,6 +560,11 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request
}
func (wfe *WebFrontEndImpl) Challenge(authz core.Authorization, response http.ResponseWriter, request *http.Request) {
err := wfe.sendNonce(response)
if err != nil {
wfe.sendError(response, "Anti-replay nonce collision", err, http.StatusInternalServerError)
}
if request.Method != "GET" && request.Method != "POST" {
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
return
@ -615,6 +672,11 @@ func (wfe *WebFrontEndImpl) Challenge(authz core.Authorization, response http.Re
}
func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *http.Request) {
err := wfe.sendNonce(response)
if err != nil {
wfe.sendError(response, "Anti-replay nonce collision", err, http.StatusInternalServerError)
}
if request.Method != "POST" {
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
return
@ -686,6 +748,11 @@ func (wfe *WebFrontEndImpl) Registration(response http.ResponseWriter, request *
}
func (wfe *WebFrontEndImpl) Authorization(response http.ResponseWriter, request *http.Request) {
err := wfe.sendNonce(response)
if err != nil {
wfe.sendError(response, "Anti-replay nonce collision", err, http.StatusInternalServerError)
}
if request.Method != "GET" && request.Method != "POST" {
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
return
@ -734,6 +801,11 @@ func (wfe *WebFrontEndImpl) Authorization(response http.ResponseWriter, request
var allHex = regexp.MustCompile("^[0-9a-f]+$")
func (wfe *WebFrontEndImpl) Certificate(response http.ResponseWriter, request *http.Request) {
err := wfe.sendNonce(response)
if err != nil {
wfe.sendError(response, "Anti-replay nonce collision", err, http.StatusInternalServerError)
}
if request.Method != "GET" && request.Method != "POST" {
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
return
@ -780,6 +852,11 @@ func (wfe *WebFrontEndImpl) Certificate(response http.ResponseWriter, request *h
}
func (wfe *WebFrontEndImpl) Terms(response http.ResponseWriter, request *http.Request) {
err := wfe.sendNonce(response)
if err != nil {
wfe.sendError(response, "Anti-replay nonce collision", err, http.StatusInternalServerError)
}
if request.Method != "GET" {
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
return
@ -789,6 +866,11 @@ func (wfe *WebFrontEndImpl) Terms(response http.ResponseWriter, request *http.Re
}
func (wfe *WebFrontEndImpl) Issuer(response http.ResponseWriter, request *http.Request) {
err := wfe.sendNonce(response)
if err != nil {
wfe.sendError(response, "Anti-replay nonce collision", err, http.StatusInternalServerError)
}
if request.Method != "GET" {
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
return
@ -804,6 +886,11 @@ func (wfe *WebFrontEndImpl) Issuer(response http.ResponseWriter, request *http.R
// BuildID tells the requestor what build we're running.
func (wfe *WebFrontEndImpl) BuildID(response http.ResponseWriter, request *http.Request) {
err := wfe.sendNonce(response)
if err != nil {
wfe.sendError(response, "Anti-replay nonce collision", err, http.StatusInternalServerError)
}
if request.Method != "GET" {
wfe.sendError(response, "Method not allowed", request.Method, http.StatusMethodNotAllowed)
return