Initial anti-replay mechanism
This commit is contained in:
parent
cf0b2e9cc2
commit
a620fe4583
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
2
start.sh
2
start.sh
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue