More refactoring for scannerAPI
parent
7e4260b8d5
commit
cb1d2343c4
|
@ -2,7 +2,6 @@ package meerkat.pollingstation;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import meerkat.pollingstation.controller.PollingStationControllerInterface;
|
import meerkat.pollingstation.controller.PollingStationControllerInterface;
|
||||||
import meerkat.pollingstation.controller.callbacks.ScanDataCallback;
|
|
||||||
import meerkat.pollingstation.controller.commands.PollingStationCommand;
|
import meerkat.pollingstation.controller.commands.PollingStationCommand;
|
||||||
import meerkat.pollingstation.controller.commands.ReceivedScanCommand;
|
import meerkat.pollingstation.controller.commands.ReceivedScanCommand;
|
||||||
import meerkat.protobuf.PollingStation;
|
import meerkat.protobuf.PollingStation;
|
||||||
|
@ -26,7 +25,7 @@ public class PollingStationMainController implements PollingStationControllerInt
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(PollingStationScanner.Consumer server) {
|
public void init(PollingStationScanner.PollingStationServer server) {
|
||||||
server.subscribe(new FutureCallback<PollingStation.ScannedData>() {
|
server.subscribe(new FutureCallback<PollingStation.ScannedData>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(PollingStation.ScannedData result) {
|
public void onSuccess(PollingStation.ScannedData result) {
|
||||||
|
|
|
@ -6,7 +6,8 @@ package meerkat.pollingstation;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.protobuf.BoolValue;
|
import com.google.protobuf.BoolValue;
|
||||||
import meerkat.protobuf.Crypto;
|
import meerkat.crypto.DigitalSignature;
|
||||||
|
import meerkat.crypto.concrete.ECDSASignature;
|
||||||
import meerkat.protobuf.PollingStation;
|
import meerkat.protobuf.PollingStation;
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
|
@ -16,24 +17,25 @@ import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB_SCANNER_CONNECT_PATH;
|
import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB_SCANNER_CONNECT_PATH;
|
||||||
import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB_SCANNER_ERROR_PATH;
|
|
||||||
import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB_SCANNER_SCAN_PATH;
|
import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB_SCANNER_SCAN_PATH;
|
||||||
import static meerkat.rest.Constants.MEDIATYPE_PROTOBUF;
|
import static meerkat.rest.Constants.MEDIATYPE_PROTOBUF;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements a Web-App interface for {@link meerkat.pollingstation.PollingStationScanner.Producer}
|
* Implements a Web-App interface for {@link PollingStationScanner.ScannerClient}
|
||||||
* This class depends on {@link meerkat.pollingstation.PollingStationWebScanner} and works in conjunction with it
|
* This class depends on {@link meerkat.pollingstation.PollingStationWebScanner} and works in conjunction with it
|
||||||
*/
|
*/
|
||||||
@Path("/")
|
@Path("/")
|
||||||
public class PollingStationScannerWebApp implements PollingStationScanner.Producer {
|
public class PollingStationScannerWebApp implements PollingStationScanner.PollingStationWebAPI {
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
ServletContext servletContext;
|
ServletContext servletContext;
|
||||||
|
|
||||||
Iterable<FutureCallback<PollingStation.ScannedData>> callbacks;
|
final static BoolValue trueValue = BoolValue.newBuilder().setValue(true).build();
|
||||||
|
final static BoolValue falseValue = BoolValue.newBuilder().setValue(false).build();
|
||||||
|
|
||||||
|
PollingStationWebScanner.ScanRequestHandler requestHandler;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called by the Jetty engine when instantiating the servlet
|
* This method is called by the Jetty engine when instantiating the servlet
|
||||||
|
@ -44,7 +46,7 @@ public class PollingStationScannerWebApp implements PollingStationScanner.Produc
|
||||||
Object context = servletContext.getAttribute(PollingStationWebScanner.CALLBACKS_ATTRIBUTE_NAME);
|
Object context = servletContext.getAttribute(PollingStationWebScanner.CALLBACKS_ATTRIBUTE_NAME);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
callbacks = (Iterable<FutureCallback<PollingStation.ScannedData>>) context;
|
requestHandler = (PollingStationWebScanner.ScanRequestHandler) context;
|
||||||
} catch (ClassCastException e) {
|
} catch (ClassCastException e) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
@ -57,7 +59,7 @@ public class PollingStationScannerWebApp implements PollingStationScanner.Produc
|
||||||
@Produces(MEDIATYPE_PROTOBUF)
|
@Produces(MEDIATYPE_PROTOBUF)
|
||||||
@Override
|
@Override
|
||||||
public BoolValue connect(PollingStation.ConnectionClientData clientData) {
|
public BoolValue connect(PollingStation.ConnectionClientData clientData) {
|
||||||
return null;
|
return requestHandler.handleConnection(clientData) ? trueValue : falseValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
|
@ -65,22 +67,8 @@ public class PollingStationScannerWebApp implements PollingStationScanner.Produc
|
||||||
@Consumes(MEDIATYPE_PROTOBUF)
|
@Consumes(MEDIATYPE_PROTOBUF)
|
||||||
@Produces(MEDIATYPE_PROTOBUF)
|
@Produces(MEDIATYPE_PROTOBUF)
|
||||||
@Override
|
@Override
|
||||||
public BoolValue newScan(PollingStation.ScannedData scannedData) {
|
public BoolValue newScan(PollingStation.SignedScannedData scannerData) {
|
||||||
|
return requestHandler.handleRequest(scannerData) ? trueValue : falseValue;
|
||||||
boolean handled = false;
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
for (FutureCallback<PollingStation.ScannedData> callback : callbacks){
|
|
||||||
|
|
||||||
callback.onSuccess(scannedData);
|
|
||||||
handled = true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return BoolValue.newBuilder()
|
|
||||||
.setValue(handled)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import org.slf4j.LoggerFactory;
|
||||||
public class PollingStationToyRun {
|
public class PollingStationToyRun {
|
||||||
final Logger logger = LoggerFactory.getLogger(getClass());
|
final Logger logger = LoggerFactory.getLogger(getClass());
|
||||||
|
|
||||||
private static PollingStationScanner.Consumer scanner;
|
private static PollingStationScanner.PollingStationServer scanner;
|
||||||
private static final String CONTEXT_PATH = "/scan";
|
private static final String CONTEXT_PATH = "/scan";
|
||||||
private static final int PORT = 8080;
|
private static final int PORT = 8080;
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
package meerkat.pollingstation;
|
package meerkat.pollingstation;
|
||||||
|
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
import meerkat.crypto.DigitalSignature;
|
||||||
|
import meerkat.crypto.concrete.ECDSASignature;
|
||||||
import meerkat.protobuf.PollingStation;
|
import meerkat.protobuf.PollingStation;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.servlet.*;
|
import org.eclipse.jetty.servlet.*;
|
||||||
|
@ -14,28 +20,128 @@ import org.glassfish.jersey.server.ResourceConfig;
|
||||||
|
|
||||||
import meerkat.protobuf.PollingStation.ScannedData;
|
import meerkat.protobuf.PollingStation.ScannedData;
|
||||||
import meerkat.rest.*;
|
import meerkat.rest.*;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Arbel on 05/05/2016.
|
* Created by Arbel on 05/05/2016.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class PollingStationWebScanner implements PollingStationScanner.Consumer {
|
public class PollingStationWebScanner implements PollingStationScanner.PollingStationServer {
|
||||||
|
|
||||||
public final static String CALLBACKS_ATTRIBUTE_NAME = "controller";
|
public final static String CALLBACKS_ATTRIBUTE_NAME = "controller";
|
||||||
|
|
||||||
|
final Logger logger = LoggerFactory.getLogger(getClass());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expected serial number for next scan
|
||||||
|
*/
|
||||||
|
long expectedSerial;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should a newly-connected scanner be verified using the nonce?
|
||||||
|
*/
|
||||||
|
boolean verifyNonce;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should every scan's signature be verified?
|
||||||
|
*/
|
||||||
|
boolean verifySignatures;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The nonce we use to verify a new scanner connection.
|
||||||
|
*/
|
||||||
|
long nonce;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
DigitalSignature verifier;
|
||||||
|
|
||||||
private final Server server;
|
private final Server server;
|
||||||
|
|
||||||
private final List<FutureCallback<ScannedData>> callbacks;
|
private final List<FutureCallback<ScannedData>> callbacks;
|
||||||
boolean verifyScanner;
|
|
||||||
byte nonce[];
|
public class ScanRequestHandler {
|
||||||
|
private ScanRequestHandler() { }
|
||||||
|
|
||||||
|
boolean handleConnection(PollingStation.ConnectionClientData data) {
|
||||||
|
if (verifyNonce) {
|
||||||
|
if (data.getNonce() != nonce) {
|
||||||
|
|
||||||
|
logger.warn("Received scanner connection with bad nonce!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verifySignatures) {
|
||||||
|
try {
|
||||||
|
if (data.getIdCase() != PollingStation.ConnectionClientData.IdCase.SCANNERPK)
|
||||||
|
throw new CertificateException("Connection request doesn't include certificate!");
|
||||||
|
|
||||||
|
verifier.loadVerificationCertificate(data.getScannerPK());
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
logger.warn("Scanner connection with bad certificate: {}", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean handleRequest(PollingStation.SignedScannedData data) {
|
||||||
|
ScannedData scannedData = data.getData();
|
||||||
|
if (verifySignatures) {
|
||||||
|
ByteString scannerID = scannedData.getScannerId();
|
||||||
|
if (!scannerID.equals(verifier.getSignerID())) {
|
||||||
|
logger.warn("Scanner ID doesn't match connection public key");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
verifier.initVerify(data.getScannerSig());
|
||||||
|
verifier.updateContent(scannedData);
|
||||||
|
if (!verifier.verify()) {
|
||||||
|
logger.warn("Bad Signature");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
logger.warn("Certificate Exception: {}", e);
|
||||||
|
return false;
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
logger.warn("InvalidKey Exception: {}", e);
|
||||||
|
return false;
|
||||||
|
} catch (SignatureException e) {
|
||||||
|
logger.warn("Signature Exception: {}", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (FutureCallback<ScannedData> callback : callbacks) {
|
||||||
|
callback.onSuccess(scannedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ScanRequestHandler scanRequestHandler;
|
||||||
|
|
||||||
|
|
||||||
public PollingStationWebScanner(int port, String contextPath) {
|
public PollingStationWebScanner(int port, String contextPath) {
|
||||||
|
|
||||||
|
scanRequestHandler = new ScanRequestHandler();
|
||||||
callbacks = new LinkedList<>();
|
callbacks = new LinkedList<>();
|
||||||
|
verifier = new ECDSASignature();
|
||||||
|
|
||||||
server = new Server(port);
|
server = new Server(port);
|
||||||
|
|
||||||
ServletContextHandler servletContextHandler = new ServletContextHandler(server, contextPath);
|
ServletContextHandler servletContextHandler = new ServletContextHandler(server, contextPath);
|
||||||
servletContextHandler.setAttribute(CALLBACKS_ATTRIBUTE_NAME, (Iterable<FutureCallback<ScannedData>>) callbacks);
|
servletContextHandler.setAttribute(CALLBACKS_ATTRIBUTE_NAME, callbacks);
|
||||||
|
|
||||||
ResourceConfig resourceConfig = new ResourceConfig(PollingStationScannerWebApp.class);
|
ResourceConfig resourceConfig = new ResourceConfig(PollingStationScannerWebApp.class);
|
||||||
resourceConfig.register(ProtobufMessageBodyReader.class);
|
resourceConfig.register(ProtobufMessageBodyReader.class);
|
||||||
|
@ -50,10 +156,8 @@ public class PollingStationWebScanner implements PollingStationScanner.Consumer
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PollingStation.ConnectionServerData start(boolean verifyScanner) throws Exception {
|
public PollingStation.ConnectionServerData start(boolean verifyScanner) throws Exception {
|
||||||
this.verifyScanner = verifyScanner;
|
this.verifyNonce = this.verifySignatures = verifyScanner;
|
||||||
nonce = new byte[32];
|
nonce = new SecureRandom().nextLong();
|
||||||
new SecureRandom().nextBytes(nonce);
|
|
||||||
|
|
||||||
|
|
||||||
server.start();
|
server.start();
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -15,8 +15,6 @@ import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
import org.glassfish.jersey.server.ResourceConfig;
|
import org.glassfish.jersey.server.ResourceConfig;
|
||||||
import org.glassfish.jersey.servlet.ServletContainer;
|
import org.glassfish.jersey.servlet.ServletContainer;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
@ -24,7 +22,7 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||||
/**
|
/**
|
||||||
* Created by Laura on 3/20/2017.
|
* Created by Laura on 3/20/2017.
|
||||||
*/
|
*/
|
||||||
public class ReceiverScanHandler implements PollingStationScanner.Consumer, Runnable {
|
public class ReceiverScanHandler implements PollingStationScanner.PollingStationServer, Runnable {
|
||||||
|
|
||||||
public final static String CALLBACKS_ATTRIBUTE_NAME = "controller";
|
public final static String CALLBACKS_ATTRIBUTE_NAME = "controller";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package meerkat.pollingstation;
|
package meerkat.pollingstation;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
|
||||||
import com.google.protobuf.BoolValue;
|
import com.google.protobuf.BoolValue;
|
||||||
import meerkat.pollingstation.controller.callbacks.ScanCallback;
|
import meerkat.pollingstation.controller.callbacks.ScanCallback;
|
||||||
import meerkat.pollingstation.controller.callbacks.ScanDataCallback;
|
import meerkat.pollingstation.controller.callbacks.ScanDataCallback;
|
||||||
|
@ -23,11 +22,11 @@ import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB
|
||||||
import static meerkat.rest.Constants.MEDIATYPE_PROTOBUF;
|
import static meerkat.rest.Constants.MEDIATYPE_PROTOBUF;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements a Web-App interface for {@link meerkat.pollingstation.PollingStationScanner.Producer}
|
* Implements a Web-App interface for {@link PollingStationScanner.ScannerClient}
|
||||||
* This class depends on {@link meerkat.pollingstation.ReceiverScanHandler} and works in conjunction with it
|
* This class depends on {@link meerkat.pollingstation.ReceiverScanHandler} and works in conjunction with it
|
||||||
*/
|
*/
|
||||||
@Path("/")
|
@Path("/")
|
||||||
public class ReceiverWebAPI implements PollingStationScanner.Producer {
|
public class ReceiverWebAPI implements PollingStationScanner.ScannerClient {
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
ServletContext servletContext;
|
ServletContext servletContext;
|
||||||
|
|
|
@ -11,7 +11,7 @@ public interface PollingStationControllerInterface extends Runnable {
|
||||||
* (see VotingBoothController)
|
* (see VotingBoothController)
|
||||||
*/
|
*/
|
||||||
// TODO: 4/16/2017 complete with proper arguments and exceptions
|
// TODO: 4/16/2017 complete with proper arguments and exceptions
|
||||||
public void init(PollingStationScanner.Consumer server);
|
public void init(PollingStationScanner.PollingStationServer server);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* an asynchronous call from Admin Console (If there is such one implemented) to shut down the system
|
* an asynchronous call from Admin Console (If there is such one implemented) to shut down the system
|
||||||
|
|
|
@ -27,7 +27,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
*/
|
*/
|
||||||
public class PollingStationWebScannerTest {
|
public class PollingStationWebScannerTest {
|
||||||
|
|
||||||
private PollingStationScanner.Consumer scanner;
|
private PollingStationScanner.PollingStationServer scanner;
|
||||||
private static final String ADDRESS = "http://localhost";
|
private static final String ADDRESS = "http://localhost";
|
||||||
private static final String SUB_ADDRESS = "";
|
private static final String SUB_ADDRESS = "";
|
||||||
private static final int PORT = 8080;
|
private static final int PORT = 8080;
|
||||||
|
|
|
@ -27,7 +27,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
*/
|
*/
|
||||||
public class ReceiverTest {
|
public class ReceiverTest {
|
||||||
|
|
||||||
private PollingStationScanner.Consumer scanner;
|
private PollingStationScanner.PollingStationServer scanner;
|
||||||
private static final String ADDRESS = "http://localhost";
|
private static final String ADDRESS = "http://localhost";
|
||||||
private static final String SUB_ADDRESS = "";
|
private static final String SUB_ADDRESS = "";
|
||||||
private static final int PORT = 8080;
|
private static final int PORT = 8080;
|
||||||
|
|
|
@ -5,20 +5,12 @@ import com.google.protobuf.ByteString;
|
||||||
import meerkat.protobuf.PollingStation;
|
import meerkat.protobuf.PollingStation;
|
||||||
import meerkat.protobuf.PollingStation.ErrorMsg;
|
import meerkat.protobuf.PollingStation.ErrorMsg;
|
||||||
import meerkat.protobuf.PollingStation.ScannedData;
|
import meerkat.protobuf.PollingStation.ScannedData;
|
||||||
import meerkat.rest.Constants;
|
|
||||||
import meerkat.rest.ProtobufMessageBodyReader;
|
|
||||||
import meerkat.rest.ProtobufMessageBodyWriter;
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.ws.rs.client.Client;
|
|
||||||
import javax.ws.rs.client.ClientBuilder;
|
|
||||||
import javax.ws.rs.client.Entity;
|
|
||||||
import javax.ws.rs.client.WebTarget;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
|
|
||||||
import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB_SCANNER_ERROR_PATH;
|
import static meerkat.pollingstation.PollingStationConstants.POLLING_STATION_WEB_SCANNER_ERROR_PATH;
|
||||||
|
@ -28,7 +20,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
public class Receiver_ClientTest {
|
public class Receiver_ClientTest {
|
||||||
final Logger logger = LoggerFactory.getLogger(getClass());
|
final Logger logger = LoggerFactory.getLogger(getClass());
|
||||||
private PollingStationScanner.Consumer scanner;
|
private PollingStationScanner.PollingStationServer scanner;
|
||||||
private static final String CONTEXT_PATH = "/scan";
|
private static final String CONTEXT_PATH = "/scan";
|
||||||
|
|
||||||
private Semaphore semaphore;
|
private Semaphore semaphore;
|
||||||
|
@ -94,7 +86,7 @@ public class Receiver_ClientTest {
|
||||||
|
|
||||||
scanner.subscribe(new ScanHandler(scannedData));
|
scanner.subscribe(new ScanHandler(scannedData));
|
||||||
|
|
||||||
PollingStationScanner.Producer scannerClient = new ScannerClientAPI()
|
PollingStationScanner.ScannerClient scannerClient = new ScannerClientAPI()
|
||||||
|
|
||||||
ScannerClientAPI(PSC_URL + POLLING_STATION_WEB_SCANNER_SCAN_PATH);
|
ScannerClientAPI(PSC_URL + POLLING_STATION_WEB_SCANNER_SCAN_PATH);
|
||||||
scannerClient.newScan(scannedData, null);
|
scannerClient.newScan(scannedData, null);
|
||||||
|
|
|
@ -15,7 +15,7 @@ public interface PollingStationScanner {
|
||||||
/**
|
/**
|
||||||
* An interface for processing scans (Polling Station side)
|
* An interface for processing scans (Polling Station side)
|
||||||
*/
|
*/
|
||||||
public interface Consumer {
|
public interface PollingStationServer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up the connection to the scanner and begins receiving scans.
|
* Sets up the connection to the scanner and begins receiving scans.
|
||||||
|
@ -40,10 +40,11 @@ public interface PollingStationScanner {
|
||||||
public void subscribe(FutureCallback<ScannedData> scanCallback);
|
public void subscribe(FutureCallback<ScannedData> scanCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface for submitting scanned data (scanner side)
|
* An interface for handling the WebAPI (on the polling-station side)
|
||||||
*/
|
*/
|
||||||
public interface Producer {
|
public interface PollingStationWebAPI {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect to the polling station server.
|
* Connect to the polling station server.
|
||||||
|
@ -54,11 +55,43 @@ public interface PollingStationScanner {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a scan to all subscribers
|
* Sends a scan to all subscribers
|
||||||
* @param scannedData contains the scanned data
|
* @param scannedData contains the scanned data.
|
||||||
* @return a BoolValue containing TRUE iff the scanned data has been sent to at least one subscriber
|
* This can be either a ScannedBallot or a ScanError;
|
||||||
|
* On the client side, this method can be called without setting
|
||||||
|
* the signature field.
|
||||||
|
* @return true iff the scanned data has been sent to at least one subscriber
|
||||||
*/
|
*/
|
||||||
public BoolValue newScan(ScannedData scannedData);
|
public BoolValue newScan(SignedScannedData scannedData);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for submitting scanned data (scanner side)
|
||||||
|
*/
|
||||||
|
public interface ScannerClient {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to the polling station server.
|
||||||
|
* The client data contains the scanner ID and optional verification data.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean connect(ConnectionServerData serverData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a scan to all subscribers
|
||||||
|
* @param scannedBallot contains the scanned data.
|
||||||
|
* This can be either a ScannedBallot or a ScanError;
|
||||||
|
* @return true iff the scanned data has been sent to at least one subscriber
|
||||||
|
*/
|
||||||
|
public boolean newScan(ScannedBallot scannedBallot);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report a scan error to all subscribers
|
||||||
|
* @param scanError
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean reportError(ScanError scanError);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,64 +1,137 @@
|
||||||
package meerkat.pollingstation;
|
package meerkat.pollingstation;
|
||||||
|
|
||||||
import com.google.protobuf.BoolValue;
|
import com.google.protobuf.BoolValue;
|
||||||
import meerkat.protobuf.Crypto;
|
import com.google.protobuf.ByteString;
|
||||||
|
import meerkat.crypto.DigitalSignatureGenerator;
|
||||||
import meerkat.protobuf.PollingStation;
|
import meerkat.protobuf.PollingStation;
|
||||||
import meerkat.rest.Constants;
|
import meerkat.rest.Constants;
|
||||||
import meerkat.rest.ProtobufMessageBodyReader;
|
import meerkat.rest.ProtobufMessageBodyReader;
|
||||||
import meerkat.rest.ProtobufMessageBodyWriter;
|
import meerkat.rest.ProtobufMessageBodyWriter;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.ws.rs.client.Client;
|
import javax.ws.rs.client.Client;
|
||||||
import javax.ws.rs.client.ClientBuilder;
|
import javax.ws.rs.client.ClientBuilder;
|
||||||
import javax.ws.rs.client.Entity;
|
import javax.ws.rs.client.Entity;
|
||||||
import javax.ws.rs.client.WebTarget;
|
import javax.ws.rs.client.WebTarget;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Laura on 3/20/2017.
|
* Created by Laura on 3/20/2017.
|
||||||
*/
|
*/
|
||||||
public class ScannerClientAPI implements PollingStationScanner.Producer {
|
public class ScannerClientAPI implements PollingStationScanner.ScannerClient {
|
||||||
private Client client;
|
final Logger logger = LoggerFactory.getLogger(getClass());
|
||||||
private WebTarget webTarget;
|
|
||||||
|
|
||||||
public ScannerClientAPI(String url) {
|
|
||||||
client = ClientBuilder.newClient();
|
|
||||||
client.register(ProtobufMessageBodyReader.class);
|
|
||||||
client.register(ProtobufMessageBodyWriter.class);
|
|
||||||
webTarget = client.target(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
DigitalSignatureGenerator signer;
|
||||||
|
ByteString scannerID;
|
||||||
|
Client client;
|
||||||
|
WebTarget webTarget;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends the scanned data to the polling station server
|
* Serial number of request (zeroed on new connection to server)
|
||||||
* @param scannedData
|
|
||||||
* @return boolean value: true if the data was sent successfully, false otherwise
|
|
||||||
*/
|
*/
|
||||||
@Override
|
long serial;
|
||||||
public BoolValue newScan(PollingStation.ScannedData scannedData) {
|
|
||||||
|
|
||||||
Response response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(scannedData, Constants.MEDIATYPE_PROTOBUF),
|
|
||||||
);
|
protected ScannerClientAPI() {
|
||||||
BoolValue res = response.readEntity(BoolValue.class);
|
this.signer = null;
|
||||||
response.close();
|
this.scannerID = null;
|
||||||
return res.getValue();
|
this.client = ClientBuilder.newClient();
|
||||||
|
this.client.register(ProtobufMessageBodyReader.class);
|
||||||
|
this.client.register(ProtobufMessageBodyWriter.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for client that doesn't use signatures.
|
||||||
|
* @param scannerId
|
||||||
|
*/
|
||||||
|
public ScannerClientAPI(byte[] scannerId) {
|
||||||
|
this();
|
||||||
|
ByteString.copyFrom(scannerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for client that signs messages.
|
||||||
|
* @param signer
|
||||||
|
*/
|
||||||
|
public ScannerClientAPI(DigitalSignatureGenerator signer) {
|
||||||
|
this();
|
||||||
|
assert (signer != null && signer.getSignerID() != null); // Use the default constructor instead
|
||||||
|
|
||||||
|
this.signer = signer;
|
||||||
|
this.scannerID = signer.getSignerID();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
client.close();
|
client.close();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BoolValue connect(PollingStation.ConnectionClientData clientData) {
|
public boolean connect(PollingStation.ConnectionServerData serverData) {
|
||||||
return null;
|
serial = 0;
|
||||||
|
webTarget = client.target(serverData.getServerUrl());
|
||||||
|
PollingStation.ConnectionClientData.Builder clientData = PollingStation.ConnectionClientData.newBuilder()
|
||||||
|
.setNonce(serverData.getNonce());
|
||||||
|
|
||||||
|
if (signer != null) {
|
||||||
|
clientData.setScannerPK(signer.getSignerPublicKey());
|
||||||
|
} else {
|
||||||
|
clientData.setScannerId(scannerID);
|
||||||
|
}
|
||||||
|
|
||||||
|
Response response = webTarget.request(Constants.MEDIATYPE_PROTOBUF)
|
||||||
|
.post(Entity.entity(clientData.build(), Constants.MEDIATYPE_PROTOBUF));
|
||||||
|
BoolValue res = response.readEntity(BoolValue.class);
|
||||||
|
response.close();
|
||||||
|
return res.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
boolean sendScannedData(PollingStation.ScannedData.Builder scannedData) {
|
||||||
|
|
||||||
|
scannedData.setSerial(serial++)
|
||||||
|
.setScannerId(scannerID);
|
||||||
|
|
||||||
|
PollingStation.SignedScannedData.Builder signedDataBuilder = PollingStation.SignedScannedData.newBuilder()
|
||||||
|
.setData(scannedData);
|
||||||
|
|
||||||
|
if (signer != null) {
|
||||||
|
try {
|
||||||
|
signer.updateContent(scannedData.build());
|
||||||
|
|
||||||
|
signedDataBuilder.setScannerSig(signer.sign());
|
||||||
|
} catch (SignatureException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Response response = webTarget.request(Constants.MEDIATYPE_PROTOBUF)
|
||||||
|
.post(Entity.entity(signedDataBuilder.build(), Constants.MEDIATYPE_PROTOBUF));
|
||||||
|
BoolValue res = response.readEntity(BoolValue.class);
|
||||||
|
response.close();
|
||||||
|
return res.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the scanned data to the polling station server
|
||||||
|
* @param scannedBallot
|
||||||
|
* @return boolean value: true if the data was sent successfully, false otherwise
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean newScan(PollingStation.ScannedBallot scannedBallot) {
|
||||||
|
PollingStation.ScannedData.Builder scannedData = PollingStation.ScannedData.newBuilder()
|
||||||
|
.setBallot(scannedBallot);
|
||||||
|
|
||||||
|
return sendScannedData(scannedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BoolValue reportScanError(PollingStation.ErrorMsg errorMsg, Crypto.Signature errorMsgSignature) {
|
public boolean reportError(PollingStation.ScanError scanError) {
|
||||||
return null;
|
PollingStation.ScannedData.Builder scannedData = PollingStation.ScannedData.newBuilder()
|
||||||
|
.setError(scanError);
|
||||||
|
|
||||||
|
return sendScannedData(scannedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,27 +8,32 @@ import "meerkat/crypto.proto";
|
||||||
import "meerkat/voting.proto";
|
import "meerkat/voting.proto";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Data sent (out-of-band) by the server that the client will require to set up a connection
|
// Data sent (out-of-band) by the server that the client will require to set up a connection
|
||||||
message ConnectionServerData {
|
message ConnectionServerData {
|
||||||
// Web-API URL for the server
|
// Web-API URL for the server
|
||||||
string server_url = 1;
|
string server_url = 1;
|
||||||
|
|
||||||
// random nonce used for verification of the client.
|
// random nonce used for verification of the client.
|
||||||
bytes nonce = 2;
|
uint64 nonce = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Data sent by the client in order to set up a connection
|
// Data sent by the client in order to set up a connection
|
||||||
message ConnectionClientData {
|
message ConnectionClientData {
|
||||||
// Key used to identify the client
|
|
||||||
SignatureVerificationKey scannerPK = 1;
|
oneof id {
|
||||||
|
// Unique ID of the scanner (if not using signatures)
|
||||||
|
bytes scannerId = 1;
|
||||||
|
|
||||||
|
// Public Key used to sign future reports
|
||||||
|
// If this is set, the ID is the certificate fingerprint.
|
||||||
|
SignatureVerificationKey scannerPK = 2;
|
||||||
|
}
|
||||||
|
|
||||||
// Nonce sent by connection-server
|
// Nonce sent by connection-server
|
||||||
bytes nonce = 2;
|
// (used to verify pairing)
|
||||||
|
uint64 nonce = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
message ScannedBallot {
|
message ScannedBallot {
|
||||||
bytes channel = 1;
|
bytes channel = 1;
|
||||||
|
|
||||||
|
@ -41,6 +46,7 @@ message ScanError {
|
||||||
string msg = 1;
|
string msg = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Container for scanned data
|
// Container for scanned data
|
||||||
message ScannedData {
|
message ScannedData {
|
||||||
oneof data {
|
oneof data {
|
||||||
|
@ -49,6 +55,10 @@ message ScannedData {
|
||||||
}
|
}
|
||||||
uint64 serial = 4; // Serial number of the message
|
uint64 serial = 4; // Serial number of the message
|
||||||
bytes scannerId = 5; // hash of the scannerPK
|
bytes scannerId = 5; // hash of the scannerPK
|
||||||
Signature scannerSig = 6; // signature on the serialzed form of data.
|
}
|
||||||
|
|
||||||
|
message SignedScannedData {
|
||||||
|
ScannedData data = 1;
|
||||||
|
Signature scannerSig = 2; // signature on the serialzed form of data.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import java.util.concurrent.Semaphore;
|
||||||
*/
|
*/
|
||||||
public class PollingStationServerToyRun {
|
public class PollingStationServerToyRun {
|
||||||
|
|
||||||
private PollingStationScanner.Consumer scanner;
|
private PollingStationScanner.PollingStationServer scanner;
|
||||||
private static final String ADDRESS = "http://localhost";
|
private static final String ADDRESS = "http://localhost";
|
||||||
private static final String SUB_ADDRESS = "";
|
private static final String SUB_ADDRESS = "";
|
||||||
private static final int PORT = 8080;
|
private static final int PORT = 8080;
|
||||||
|
|
|
@ -32,7 +32,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
public class NetworkVirtualPrinterTest {
|
public class NetworkVirtualPrinterTest {
|
||||||
|
|
||||||
private PollingStationScanner.Consumer scanner;
|
private PollingStationScanner.PollingStationServer scanner;
|
||||||
private static final String ADDRESS = "http://localhost";
|
private static final String ADDRESS = "http://localhost";
|
||||||
private static final String SUB_ADDRESS = "";
|
private static final String SUB_ADDRESS = "";
|
||||||
private static final int PORT = 8080;
|
private static final int PORT = 8080;
|
||||||
|
|
Loading…
Reference in New Issue