Java tutorial
/** * Copyright 2013 Crypto Workshop Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.cryptoworkshop.ximix.node.mixnet.service; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Constructor; import java.security.KeyStore; import java.security.PrivateKey; import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.FutureTask; import java.util.concurrent.atomic.AtomicLong; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1Object; import org.bouncycastle.asn1.DERNull; import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.DERUTF8String; import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; import org.bouncycastle.cms.CMSProcessableByteArray; import org.bouncycastle.cms.CMSSignedDataGenerator; import org.bouncycastle.cms.CMSSignedDataStreamGenerator; import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder; import org.bouncycastle.crypto.Commitment; import org.bouncycastle.crypto.commitments.GeneralHashCommitter; import org.bouncycastle.crypto.digests.SHA512Digest; import org.bouncycastle.util.encoders.Hex; import org.cryptoworkshop.ximix.client.connection.ServiceConnectionException; import org.cryptoworkshop.ximix.client.connection.ServicesConnection; import org.cryptoworkshop.ximix.common.asn1.message.BoardCapabilities; import org.cryptoworkshop.ximix.common.asn1.message.BoardDetailMessage; import org.cryptoworkshop.ximix.common.asn1.message.BoardDownloadMessage; import org.cryptoworkshop.ximix.common.asn1.message.BoardErrorStatusMessage; import org.cryptoworkshop.ximix.common.asn1.message.BoardMessage; import org.cryptoworkshop.ximix.common.asn1.message.BoardStatusMessage; import org.cryptoworkshop.ximix.common.asn1.message.BoardUploadBlockMessage; import org.cryptoworkshop.ximix.common.asn1.message.BoardUploadIndexedMessage; import org.cryptoworkshop.ximix.common.asn1.message.BoardUploadMessage; import org.cryptoworkshop.ximix.common.asn1.message.CapabilityMessage; import org.cryptoworkshop.ximix.common.asn1.message.ClientMessage; import org.cryptoworkshop.ximix.common.asn1.message.CommandMessage; import org.cryptoworkshop.ximix.common.asn1.message.CopyAndMoveMessage; import org.cryptoworkshop.ximix.common.asn1.message.CreateBoardMessage; import org.cryptoworkshop.ximix.common.asn1.message.ErrorMessage; import org.cryptoworkshop.ximix.common.asn1.message.Message; import org.cryptoworkshop.ximix.common.asn1.message.MessageCommitment; import org.cryptoworkshop.ximix.common.asn1.message.MessageReply; import org.cryptoworkshop.ximix.common.asn1.message.MessageType; import org.cryptoworkshop.ximix.common.asn1.message.PermuteAndMoveMessage; import org.cryptoworkshop.ximix.common.asn1.message.PostedData; import org.cryptoworkshop.ximix.common.asn1.message.PostedMessage; import org.cryptoworkshop.ximix.common.asn1.message.PostedMessageBlock; import org.cryptoworkshop.ximix.common.asn1.message.SeedAndWitnessMessage; import org.cryptoworkshop.ximix.common.asn1.message.SeedCommitmentMessage; import org.cryptoworkshop.ximix.common.asn1.message.SeedMessage; import org.cryptoworkshop.ximix.common.asn1.message.TranscriptBlock; import org.cryptoworkshop.ximix.common.asn1.message.TranscriptDownloadMessage; import org.cryptoworkshop.ximix.common.asn1.message.TranscriptQueryMessage; import org.cryptoworkshop.ximix.common.asn1.message.TranscriptQueryResponse; import org.cryptoworkshop.ximix.common.asn1.message.TranscriptTransferMessage; import org.cryptoworkshop.ximix.common.asn1.message.TransitBoardMessage; import org.cryptoworkshop.ximix.common.config.Config; import org.cryptoworkshop.ximix.common.config.ConfigException; import org.cryptoworkshop.ximix.common.config.ConfigObjectFactory; import org.cryptoworkshop.ximix.common.util.EventNotifier; import org.cryptoworkshop.ximix.common.util.IndexNumberGenerator; import org.cryptoworkshop.ximix.common.util.TranscriptType; import org.cryptoworkshop.ximix.common.util.challenge.PairedChallenger; import org.cryptoworkshop.ximix.common.util.challenge.SeededChallenger; import org.cryptoworkshop.ximix.common.util.challenge.SerialChallenger; import org.cryptoworkshop.ximix.node.mixnet.board.BulletinBoard; import org.cryptoworkshop.ximix.node.mixnet.board.BulletinBoardRegistry; import org.cryptoworkshop.ximix.node.mixnet.shuffle.CopyAndMoveTask; import org.cryptoworkshop.ximix.node.mixnet.shuffle.TransformShuffleAndMoveTask; import org.cryptoworkshop.ximix.node.mixnet.transform.Transform; import org.cryptoworkshop.ximix.node.service.BasicNodeService; import org.cryptoworkshop.ximix.node.service.Decoupler; import org.cryptoworkshop.ximix.node.service.NodeContext; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * Service class for hosting bulletin boards. */ public class BoardHostingService extends BasicNodeService { private final Executor decoupler; private final BulletinBoardRegistry boardRegistry; private final AtomicLong queryCounter = new AtomicLong(0L); private final Constructor witnessChallengerConstructor; private final Map<String, IndexNumberGenerator> challengers = new HashMap<>(); private final Map<String, TranscriptGenerator> transcriptGenerators = new HashMap<>(); private final Map<String, byte[][]> seedsAndWitnesses = new HashMap<>(); private final BoardExecutor boardExecutor; /** * Base constructor. * * @param nodeContext the context for the node we are in. * @param config source of config information if required. */ public BoardHostingService(NodeContext nodeContext, Config config) throws ConfigException { super(nodeContext); this.decoupler = nodeContext.getDecoupler(Decoupler.SERVICES); this.boardExecutor = new BoardExecutor(decoupler, nodeContext.getExecutorService()); Map<String, Transform> transforms; if (config.hasConfig("transforms")) { List<TransformConfig> transformList = config.getConfigObjects("transforms", new TransformConfigFactory()); transforms = transformList.get(0).getTransforms(); } else { transforms = new HashMap<>(); } if (config.hasConfig("challenger")) { witnessChallengerConstructor = config.getConfigObject("challenger", new ChallengerFactory()); } else { try { witnessChallengerConstructor = SeededChallenger.class.getConstructor(Integer.class, Integer.class, byte[].class); } catch (NoSuchMethodException e) { throw new ConfigException("Cannot create witness challenge constructor: " + e.getMessage(), e); } } this.boardRegistry = new BulletinBoardRegistry(nodeContext, transforms, statistics); statistics.ensurePlaceholders(); } public CapabilityMessage getCapability() { String[] names = boardRegistry.getBoardNames(); BoardCapabilities[] details = new BoardCapabilities[names.length]; Set<String> transformNames = boardRegistry.getTransformNames(); int count = 0; for (String name : names) { details[count++] = new BoardCapabilities(name, transformNames); } return new CapabilityMessage(CapabilityMessage.Type.BOARD_HOSTING, details); } public MessageReply handle(final Message message) { FutureTask<MessageReply> future = submitToHandle(message); try { return future.get(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (ExecutionException e) { nodeContext.getEventNotifier().notify(EventNotifier.Level.ERROR, e); } return new MessageReply(MessageReply.Type.ERROR, new DERUTF8String("Future failed to evaluate on " + nodeContext.getName())); } private FutureTask<MessageReply> submitToHandle(Message message) { if (message instanceof CommandMessage) { switch (((CommandMessage) message).getType()) { case GENERATE_SEED: final SeedMessage seedMessage = SeedMessage.getInstance(message.getPayload()); return boardExecutor.submitTask(seedMessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { String seedKey = seedMessage.getBoardName() + "." + seedMessage.getOperationNumber(); if (seedsAndWitnesses.containsKey(seedKey)) { return new MessageReply(MessageReply.Type.ERROR, new ErrorMessage("Duplicate seed generation request for operation " + seedMessage.getOperationNumber())); } // TODO: specify source of randomness SecureRandom random = new SecureRandom(); byte[] seed = new byte[64]; // largest we can manage with SHA-512 random.nextBytes(seed); GeneralHashCommitter sha512Committer = new GeneralHashCommitter(new SHA512Digest(), random); Commitment commitment = sha512Committer.commit(seed); seedsAndWitnesses.put(seedKey, new byte[][] { seed, commitment.getSecret() }); SeedCommitmentMessage seedCommitmentMessage = new SeedCommitmentMessage( seedMessage.getBoardName(), seedMessage.getOperationNumber(), commitment.getCommitment()); CMSSignedDataGenerator cmsGen = new CMSSignedDataGenerator(); KeyStore nodeCAStore = nodeContext.getNodeCAStore(); Certificate[] nodeCerts = nodeCAStore.getCertificateChain("nodeCA"); cmsGen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC") .build("SHA256withECDSA", (PrivateKey) nodeCAStore.getKey("nodeCA", new char[0]), (X509Certificate) nodeCerts[0])); for (Certificate cert : nodeCerts) { cmsGen.addCertificate(new JcaX509CertificateHolder((X509Certificate) cert)); } return new MessageReply(MessageReply.Type.OKAY, cmsGen .generate(new CMSProcessableByteArray(seedCommitmentMessage.getEncoded()), true) .toASN1Structure()); } }); case FETCH_SEED: final SeedMessage seedFetchMessage = SeedMessage.getInstance(message.getPayload()); return boardExecutor.submitTask(seedFetchMessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { String seedKey = seedFetchMessage.getBoardName() + "." + seedFetchMessage.getOperationNumber(); byte[][] seedAndWitness = seedsAndWitnesses.get(seedKey); if (seedAndWitness == null) { return new MessageReply(MessageReply.Type.ERROR, new ErrorMessage("Unknown seed requested for key: " + seedKey)); } return new MessageReply(MessageReply.Type.OKAY, new SeedAndWitnessMessage(seedAndWitness[0], seedAndWitness[1])); } }); case GET_BOARD_DETAILS: Callable<MessageReply> replyCallable = new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { String[] boardNames = boardRegistry.getBoardNames(); BoardDetailMessage[] details = new BoardDetailMessage[boardNames.length]; int count = 0; for (String boardName : boardNames) { BulletinBoard board = boardRegistry.getBoard(boardName); details[count++] = new BoardDetailMessage(boardName, nodeContext.getName(), board.size(), board.getBackupHost()); } return new MessageReply(MessageReply.Type.OKAY, new DERSequence(details)); } }; FutureTask<MessageReply> task = new FutureTask<>(replyCallable); boardExecutor.execute(task); return task; case GET_BOARD_HOST: final BoardMessage boardMessage = BoardMessage.getInstance(message.getPayload()); return boardExecutor.submitTask(boardMessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { String boardHost = nodeContext.getBoardHost(boardMessage.getBoardName()); if (boardHost != null) { return new MessageReply(MessageReply.Type.OKAY, new DERUTF8String(boardHost)); } else { return new MessageReply(MessageReply.Type.OKAY, DERNull.INSTANCE); } } }); case ACTIVATE_BOARD: final BoardMessage activateBoardMessage = BoardMessage.getInstance(message.getPayload()); return boardExecutor.submitTask(activateBoardMessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { boardRegistry.activateBoard(activateBoardMessage.getBoardName()); return new MessageReply(MessageReply.Type.OKAY, new DERUTF8String(nodeContext.getName())); } }); case SUSPEND_BOARD: final BoardMessage suspendBoardMessage = BoardMessage.getInstance(message.getPayload()); return boardExecutor.submitTask(suspendBoardMessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { if (boardRegistry.isSuspended(suspendBoardMessage.getBoardName())) { return new MessageReply(MessageReply.Type.ERROR, new BoardErrorStatusMessage( suspendBoardMessage.getBoardName(), BoardErrorStatusMessage.Status.SUSPENDED)); } boardRegistry.suspendBoard(suspendBoardMessage.getBoardName()); return new MessageReply(MessageReply.Type.OKAY, new DERUTF8String(nodeContext.getName())); } }); case BOARD_CREATE: final CreateBoardMessage createBoardMessage = CreateBoardMessage.getInstance(message.getPayload()); return boardExecutor.submitTask(createBoardMessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { if (boardRegistry.hasBoard(createBoardMessage.getBoardName())) { return new MessageReply(MessageReply.Type.ERROR, new BoardErrorStatusMessage(createBoardMessage.getBoardName(), BoardErrorStatusMessage.Status.ALREADY_EXISTS)); } if (createBoardMessage.getBackUpHost() != null) { boardRegistry.createBoard(createBoardMessage.getBoardName(), createBoardMessage.getBackUpHost()); } else { boardRegistry.createBoard(createBoardMessage.getBoardName()); } return new MessageReply(MessageReply.Type.OKAY, new DERUTF8String(nodeContext.getName())); } }); case BACKUP_BOARD_CREATE: final BoardMessage backupBoardCreateMessage = BoardMessage.getInstance(message.getPayload()); return boardExecutor.submitTask(backupBoardCreateMessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { boardRegistry.createBackupBoard(backupBoardCreateMessage.getBoardName()); return new MessageReply(MessageReply.Type.OKAY, new DERUTF8String(nodeContext.getName())); } }); case BOARD_DOWNLOAD_LOCK: final BoardMessage downloadLockBoardMessage = BoardMessage.getInstance(message.getPayload()); return boardExecutor.submitTask(downloadLockBoardMessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { if (boardRegistry.isLocked(downloadLockBoardMessage.getBoardName())) { return new MessageReply(MessageReply.Type.ERROR, new BoardErrorStatusMessage(downloadLockBoardMessage.getBoardName(), BoardErrorStatusMessage.Status.SUSPENDED)); } boardRegistry.downloadLock(downloadLockBoardMessage.getBoardName()); return new MessageReply(MessageReply.Type.OKAY, new DERUTF8String(nodeContext.getName())); } }); case BOARD_DOWNLOAD_UNLOCK: final BoardMessage downloadUnlockBoardMessage = BoardMessage.getInstance(message.getPayload()); return boardExecutor.submitTask(downloadUnlockBoardMessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { boardRegistry.downloadUnlock(downloadUnlockBoardMessage.getBoardName()); return new MessageReply(MessageReply.Type.OKAY, new DERUTF8String(nodeContext.getName())); } }); case BOARD_SHUFFLE_LOCK: final BoardMessage shuffleLockBoardMessage = BoardMessage.getInstance(message.getPayload()); return boardExecutor.submitTask(shuffleLockBoardMessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { if (boardRegistry.isLocked(shuffleLockBoardMessage.getBoardName())) { return new MessageReply(MessageReply.Type.ERROR, new BoardErrorStatusMessage(shuffleLockBoardMessage.getBoardName(), BoardErrorStatusMessage.Status.SUSPENDED)); } boardRegistry.shuffleLock(shuffleLockBoardMessage.getBoardName()); return new MessageReply(MessageReply.Type.OKAY, new DERUTF8String(nodeContext.getName())); } }); case BOARD_SHUFFLE_UNLOCK: final BoardMessage shuffleUnlockBoardMessage = BoardMessage.getInstance(message.getPayload()); return boardExecutor.submitTask(shuffleUnlockBoardMessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { boardRegistry.shuffleUnlock(shuffleUnlockBoardMessage.getBoardName()); return new MessageReply(MessageReply.Type.OKAY, new DERUTF8String(nodeContext.getName())); } }); case FETCH_BOARD_STATUS: final TransitBoardMessage transitBoardMessage = TransitBoardMessage .getInstance(message.getPayload()); return boardExecutor.submitTask(transitBoardMessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { if (boardRegistry.isInTransit(transitBoardMessage.getOperationNumber(), transitBoardMessage.getBoardName(), transitBoardMessage.getStepNumber())) { return new MessageReply(MessageReply.Type.OKAY, new BoardStatusMessage( transitBoardMessage.getBoardName(), BoardStatusMessage.Status.IN_TRANSIT)); } if (boardRegistry.isComplete(transitBoardMessage.getOperationNumber(), transitBoardMessage.getBoardName(), transitBoardMessage.getStepNumber())) { return new MessageReply(MessageReply.Type.OKAY, new BoardStatusMessage( transitBoardMessage.getBoardName(), BoardStatusMessage.Status.COMPLETE)); } return new MessageReply(MessageReply.Type.OKAY, new BoardStatusMessage( transitBoardMessage.getBoardName(), BoardStatusMessage.Status.UNKNOWN)); } }); case FETCH_BOARD_COMPLETION_STATUS: final BoardMessage compStatusBoardMessage = BoardMessage.getInstance(message.getPayload()); return boardExecutor.submitTask(compStatusBoardMessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { if (boardRegistry.isLocked(compStatusBoardMessage.getBoardName())) { return new MessageReply(MessageReply.Type.OKAY, new BoardStatusMessage(compStatusBoardMessage.getBoardName(), BoardStatusMessage.Status.IN_TRANSIT)); } return new MessageReply(MessageReply.Type.OKAY, new BoardStatusMessage( compStatusBoardMessage.getBoardName(), BoardStatusMessage.Status.COMPLETE)); } }); case START_SHUFFLE_AND_MOVE_BOARD_TO_NODE: final CopyAndMoveMessage startPandMmessage = CopyAndMoveMessage.getInstance(message.getPayload()); return boardExecutor.submitTask(startPandMmessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { if (!boardRegistry.isShuffleLocked(startPandMmessage.getBoardName())) { return new MessageReply(MessageReply.Type.ERROR, new BoardErrorStatusMessage(startPandMmessage.getBoardName(), BoardErrorStatusMessage.Status.NOT_SHUFFLE_LOCKED)); } nodeContext.execute(new CopyAndMoveTask(nodeContext, boardRegistry, getPeerConnection(startPandMmessage.getDestinationNode()), startPandMmessage)); return new MessageReply(MessageReply.Type.OKAY, new DERUTF8String(nodeContext.getName())); } }); case SHUFFLE_AND_MOVE_BOARD_TO_NODE: final PermuteAndMoveMessage pAndmMessage = PermuteAndMoveMessage.getInstance(message.getPayload()); return boardExecutor.submitTask(pAndmMessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { nodeContext.execute(new TransformShuffleAndMoveTask(nodeContext, boardRegistry, getPeerConnection(pAndmMessage.getDestinationNode()), pAndmMessage)); return new MessageReply(MessageReply.Type.OKAY, new DERUTF8String(nodeContext.getName())); } }); case RETURN_TO_BOARD: final TransitBoardMessage returnToBoardMessage = TransitBoardMessage .getInstance(message.getPayload()); return boardExecutor.submitTask(returnToBoardMessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { nodeContext .execute(new ReturnToBoardTask(nodeContext, boardRegistry, returnToBoardMessage)); return new MessageReply(MessageReply.Type.OKAY, new DERUTF8String(nodeContext.getName())); } }); case INITIATE_INTRANSIT_BOARD: final TransitBoardMessage initiateTransitBoardMessage = TransitBoardMessage .getInstance(message.getPayload()); return boardExecutor.submitTask(initiateTransitBoardMessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { boardRegistry.markInTransit(initiateTransitBoardMessage.getOperationNumber(), initiateTransitBoardMessage.getBoardName(), initiateTransitBoardMessage.getStepNumber()); boardRegistry.getTransitBoard(initiateTransitBoardMessage.getOperationNumber(), initiateTransitBoardMessage.getBoardName(), initiateTransitBoardMessage.getStepNumber()).clear(); return new MessageReply(MessageReply.Type.OKAY, new DERUTF8String(nodeContext.getName())); } }); case TRANSFER_TO_BOARD: final BoardUploadBlockMessage uploadMessage = BoardUploadBlockMessage .getInstance(message.getPayload()); return boardExecutor.submitTask(uploadMessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { boardRegistry.markInTransit(uploadMessage.getOperationNumber(), uploadMessage.getBoardName(), uploadMessage.getStepNumber()); boardRegistry .getTransitBoard(uploadMessage.getOperationNumber(), uploadMessage.getBoardName(), uploadMessage.getStepNumber()) .postMessageBlock(uploadMessage.getMessageBlock()); return new MessageReply(MessageReply.Type.OKAY, new DERUTF8String(nodeContext.getName())); } }); case UPLOAD_TO_BOARD: final BoardUploadBlockMessage uploadToBoardMessage = BoardUploadBlockMessage .getInstance(message.getPayload()); return boardExecutor.submitTask(uploadToBoardMessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() { boardRegistry.markInTransit(uploadToBoardMessage.getOperationNumber(), uploadToBoardMessage.getBoardName(), uploadToBoardMessage.getStepNumber()); boardRegistry.getBoard(uploadToBoardMessage.getBoardName()) .postMessageBlock(uploadToBoardMessage.getMessageBlock()); return new MessageReply(MessageReply.Type.OKAY, new DERUTF8String(nodeContext.getName())); } }); case CLEAR_BACKUP_BOARD: final BoardMessage backupBoardMessage = BoardMessage.getInstance(message.getPayload()); return boardExecutor.submitBackupTask(backupBoardMessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() { // TODO: maybe backup the current backup locally? boardRegistry.getBackupBoard(backupBoardMessage.getBoardName()).clear(); return new MessageReply(MessageReply.Type.OKAY, new DERUTF8String(nodeContext.getName())); } }); case TRANSFER_TO_BACKUP_BOARD: final BoardUploadIndexedMessage uploadIndexedMessage = BoardUploadIndexedMessage .getInstance(message.getPayload()); return boardExecutor.submitBackupTask(uploadIndexedMessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() { boardRegistry.getBackupBoard(uploadIndexedMessage.getBoardName()) .postMessages(uploadIndexedMessage.getData()); return new MessageReply(MessageReply.Type.OKAY, new DERUTF8String(nodeContext.getName())); } }); case TRANSFER_TO_BOARD_ENDED: final TransitBoardMessage transferToBoardEndedMessage = TransitBoardMessage .getInstance(message.getPayload()); return boardExecutor.submitTask(transferToBoardEndedMessage.getBoardName(), new Callable<MessageReply>() { public MessageReply call() { boardRegistry.markCompleted(transferToBoardEndedMessage.getOperationNumber(), transferToBoardEndedMessage.getBoardName(), transferToBoardEndedMessage.getStepNumber()); return new MessageReply(MessageReply.Type.OKAY, new DERUTF8String(nodeContext.getName())); } }); case DOWNLOAD_BOARD_CONTENTS: final BoardDownloadMessage downloadRequest = BoardDownloadMessage.getInstance(message.getPayload()); return boardExecutor.submitTask(downloadRequest.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() { if (!boardRegistry.isDownloadLocked(downloadRequest.getBoardName())) { return new MessageReply(MessageReply.Type.ERROR, new BoardErrorStatusMessage(downloadRequest.getBoardName(), BoardErrorStatusMessage.Status.NOT_DOWNLOAD_LOCKED)); } BulletinBoard board = boardRegistry.getBoard(downloadRequest.getBoardName()); PostedMessageBlock messages = board.removeMessages( new PostedMessageBlock.Builder(downloadRequest.getMaxNumberOfMessages())); return new MessageReply(MessageReply.Type.OKAY, messages); } }); case DOWNLOAD_SHUFFLE_TRANSCRIPT: final TranscriptDownloadMessage transcriptDownloadMessage = TranscriptDownloadMessage .getInstance(message.getPayload()); final BulletinBoard transitBoard = boardRegistry.getTransitBoard( transcriptDownloadMessage.getOperationNumber(), transcriptDownloadMessage.getStepNo()); return boardExecutor.submitTask(transitBoard.getName(), new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { boolean isCopyBoard = isCopyBoard(transitBoard); String challengerKey = getChallengerKey(transcriptDownloadMessage, isCopyBoard || (transitBoard.size() == 1)); IndexNumberGenerator challenger = challengers.get(challengerKey); if (challenger == null) { if (transitBoard.size() == 1) { challenger = new SerialChallenger(1, transcriptDownloadMessage.getStepNo(), transcriptDownloadMessage.getSeed()); } else if (TranscriptType.GENERAL == transcriptDownloadMessage.getType()) { challenger = new SerialChallenger( transitBoard.transcriptSize(TranscriptType.GENERAL), transcriptDownloadMessage.getStepNo(), transcriptDownloadMessage.getSeed()); } else { SHA512Digest seedDigest = new SHA512Digest(); byte[] challengeSeed = new byte[seedDigest.getDigestSize()]; if (transcriptDownloadMessage.getSeed() != null) { byte[] originalSeed = transcriptDownloadMessage.getSeed(); nodeContext.getEventNotifier().notify(EventNotifier.Level.INFO, "Original seed: " + new String(Hex.encode(originalSeed))); // we follow the formulation in "Randomized Partial Checking Revisited" where the seed is // modified by the step number, the one difference being that in our case this will only take // place at the start of a pairing, or on an individual step. seedDigest.update(originalSeed, 0, originalSeed.length); int stepNo = transcriptDownloadMessage.getStepNo(); seedDigest.update((byte) (stepNo >>> 24)); seedDigest.update((byte) (stepNo >>> 16)); seedDigest.update((byte) (stepNo >>> 8)); seedDigest.update((byte) stepNo); seedDigest.doFinal(challengeSeed, 0); } nodeContext.getEventNotifier().notify(EventNotifier.Level.INFO, "Challenge seed: " + transcriptDownloadMessage.getStepNo() + " " + new String(Hex.encode(challengeSeed))); try { if (isCopyBoard) { challenger = new SerialChallenger( transitBoard.transcriptSize(transcriptDownloadMessage.getType()), transcriptDownloadMessage.getStepNo(), challengeSeed); } else if (transcriptDownloadMessage.isWithPairing()) { // TODO: maybe configure int chunkSize = 100; IndexNumberGenerator sourceGenerator = new SerialChallenger( transitBoard.size(), 0, null); int[] indexes = new int[transitBoard.size()]; int count = 0; while (sourceGenerator.hasNext()) { TranscriptBlock transcript = transitBoard.fetchTranscriptData( TranscriptType.WITNESSES, sourceGenerator, new TranscriptBlock.Builder(0, chunkSize)); for (Enumeration en = transcript.getDetails().getObjects(); en .hasMoreElements();) { PostedData msg = PostedData.getInstance(en.nextElement()); indexes[count++] = MessageCommitment.getInstance(msg.getData()) .getNewIndex(); } } challenger = new PairedChallenger(indexes, transcriptDownloadMessage.getStepNo(), (IndexNumberGenerator) witnessChallengerConstructor.newInstance( transitBoard.transcriptSize( transcriptDownloadMessage.getType()), transcriptDownloadMessage.getStepNo(), challengeSeed)); } else { challenger = (IndexNumberGenerator) witnessChallengerConstructor .newInstance( transitBoard.transcriptSize( transcriptDownloadMessage.getType()), transcriptDownloadMessage.getStepNo(), challengeSeed); } } catch (Exception e) { nodeContext.getEventNotifier().notify(EventNotifier.Level.ERROR, e); return new MessageReply(MessageReply.Type.ERROR, new ErrorMessage( "Unable to create challenger on " + nodeContext.getName())); } } challengers.put(challengerKey, challenger); } if (challenger instanceof PairedChallenger) { ((PairedChallenger) challenger).setStepNo(transcriptDownloadMessage.getStepNo()); } TranscriptBlock transcriptBlock = transitBoard.fetchTranscriptData( transcriptDownloadMessage.getType(), challenger, new TranscriptBlock.Builder(transcriptDownloadMessage.getStepNo(), transcriptDownloadMessage.getMaxNumberOfMessages())); String generatorKey = getTranscriptGeneratorKey(transcriptDownloadMessage); TranscriptGenerator transGen = transcriptGenerators.get(generatorKey); if (transGen == null) { transGen = new TranscriptGenerator(); transcriptGenerators.put(generatorKey, transGen); } if (transcriptBlock.size() != 0) { for (Enumeration en = transcriptBlock.getDetails().getObjects(); en .hasMoreElements();) { transGen.writeFragment(((ASN1Object) en.nextElement()).getEncoded()); } return new MessageReply(MessageReply.Type.OKAY, new TranscriptTransferMessage( transcriptBlock.getStepNo(), transGen.getFragment())); } if (transGen.hasData()) { transGen.finish(); return new MessageReply(MessageReply.Type.OKAY, new TranscriptTransferMessage( transcriptBlock.getStepNo(), transGen.getFragment())); } // end of data return new MessageReply(MessageReply.Type.OKAY, new TranscriptTransferMessage(transcriptBlock.getStepNo())); } }); case DOWNLOAD_SHUFFLE_TRANSCRIPT_STEPS: final TranscriptQueryMessage transcriptQueryMessage = TranscriptQueryMessage .getInstance(message.getPayload()); FutureTask<MessageReply> dstsTask = new FutureTask(new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { List<String> transitBoardNames = boardRegistry .getTransitBoardNames(transcriptQueryMessage.getOperationNumber()); int[] stepNos = new int[transitBoardNames.size()]; String boardName = ""; for (int i = 0; i != stepNos.length; i++) { String name = transitBoardNames.get(i); boardName = name.substring(name.indexOf('.') + 1, name.lastIndexOf('.')); stepNos[i] = Integer.parseInt(name.substring(name.lastIndexOf('.') + 1)); } return new MessageReply(MessageReply.Type.OKAY, new TranscriptQueryResponse(queryCounter.incrementAndGet(), boardName, stepNos)); } }); nodeContext.getExecutorService().submit(dstsTask); return dstsTask; default: FutureTask<MessageReply> eTask = new FutureTask(new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { return new MessageReply(MessageReply.Type.ERROR, new ErrorMessage("Unknown command")); } }); nodeContext.getExecutorService().submit(eTask); return eTask; } } else { switch (((ClientMessage) message).getType()) { case UPLOAD_TO_BOARD: final BoardUploadMessage uploadMessage = BoardUploadMessage.getInstance(message.getPayload()); return boardExecutor.submitTask(uploadMessage.getBoardName(), new Callable<MessageReply>() { @Override public MessageReply call() { if (boardRegistry.isLocked(uploadMessage.getBoardName())) { return new MessageReply(MessageReply.Type.ERROR, new BoardErrorStatusMessage( uploadMessage.getBoardName(), BoardErrorStatusMessage.Status.SUSPENDED)); } byte[][] messages = uploadMessage.getData(); if (messages.length == 1) { boardRegistry.getBoard(uploadMessage.getBoardName()).postMessage(messages[0]); } else { boardRegistry.getBoard(uploadMessage.getBoardName()).postMessages(messages); } return new MessageReply(MessageReply.Type.OKAY, new DERUTF8String(nodeContext.getName())); } }); default: FutureTask<MessageReply> eTask = new FutureTask(new Callable<MessageReply>() { @Override public MessageReply call() throws Exception { return new MessageReply(MessageReply.Type.ERROR, new DERUTF8String("Unknown command")); } }); nodeContext.getExecutorService().submit(eTask); return eTask; } } } private String getChallengerKey(TranscriptDownloadMessage transcriptDownloadMessage, boolean isCopyBoard) { if (transcriptDownloadMessage.isWithPairing() && transcriptDownloadMessage.getType() != TranscriptType.GENERAL && !isCopyBoard) { return Long.toString(transcriptDownloadMessage.getQueryID()); } return transcriptDownloadMessage.getQueryID() + "." + transcriptDownloadMessage.getStepNo(); } private String getTranscriptGeneratorKey(TranscriptDownloadMessage transcriptDownloadMessage) { return transcriptDownloadMessage.getQueryID() + "." + transcriptDownloadMessage.getStepNo(); } private boolean isCopyBoard(BulletinBoard transitBoard) { IndexNumberGenerator sourceGenerator = new SerialChallenger(1, 0, null); while (sourceGenerator.hasNext()) { TranscriptBlock transcript = transitBoard.fetchTranscriptData(TranscriptType.WITNESSES, sourceGenerator, new TranscriptBlock.Builder(0, 1)); for (Enumeration en = transcript.getDetails().getObjects(); en.hasMoreElements();) { PostedData msg = PostedData.getInstance(en.nextElement()); return MessageCommitment.getInstance(msg.getData()) == null; } } return true; // no commitment data present } private ServicesConnection getPeerConnection(String destinationNode) { // return a proxy for ourselves. if (nodeContext.getName().equals(destinationNode)) { return new ServicesConnection() { @Override public void activate() throws ServiceConnectionException { // ignore } @Override public CapabilityMessage[] getCapabilities() { return new CapabilityMessage[] { BoardHostingService.this.getCapability() }; } @Override public EventNotifier getEventNotifier() { return nodeContext.getEventNotifier(); } @Override public MessageReply sendMessage(MessageType type, ASN1Encodable messagePayload) throws ServiceConnectionException { return BoardHostingService.this .handle(new CommandMessage((CommandMessage.Type) type, messagePayload)); } @Override public void shutdown() throws ServiceConnectionException { // ignore } }; } return nodeContext.getPeerMap().get(destinationNode); } public boolean isAbleToHandle(Message message) { return new MessageEvaluator(getCapability()).isAbleToHandle(message); } private static class ChallengerFactory implements ConfigObjectFactory<Constructor> { private Throwable throwable; public Constructor createObject(Node configNode) throws ConfigException { NodeList xmlNodes = configNode.getChildNodes(); for (int i = 0; i != xmlNodes.getLength(); i++) { Node xmlNode = xmlNodes.item(i); if (xmlNode.getNodeName().equals("implementation")) { try { Class clazz = Class.forName(xmlNode.getTextContent().trim()); Constructor constructor = clazz.getConstructor(Integer.class, Integer.class, byte[].class); return constructor; } catch (Exception e) { throw new ConfigException("Unable to create Challenger: " + e.getMessage(), e); } } } return null; } } private static class TransformConfigFactory implements ConfigObjectFactory<TransformConfig> { public TransformConfig createObject(Node configNode) throws ConfigException { return new TransformConfig(configNode); } } private static class TransformConfig { private Map<String, Transform> transforms = new HashMap<>(); public TransformConfig(Node configNode) throws ConfigException { NodeList xmlNodes = configNode.getChildNodes(); for (int i = 0; i != xmlNodes.getLength(); i++) { Node xmlNode = xmlNodes.item(i); if (xmlNode.getNodeName().equals("transform")) { try { Class clazz = Class.forName(xmlNode.getTextContent().trim()); Constructor constructor = clazz.getConstructor(); Transform impl = (Transform) constructor.newInstance(); transforms.put(impl.getName(), impl); } catch (Exception e) { throw new ConfigException("Unable to create Transform: " + e.getMessage(), e); } } } } public Map<String, Transform> getTransforms() { return transforms; } } private class ReturnToBoardTask implements Runnable { private final NodeContext nodeContext; private final TransitBoardMessage transitBoardMessage; private final BulletinBoardRegistry boardRegistry; public ReturnToBoardTask(NodeContext nodeContext, BulletinBoardRegistry boardRegistry, TransitBoardMessage transitBoardMessage) { this.nodeContext = nodeContext; this.boardRegistry = boardRegistry; this.transitBoardMessage = transitBoardMessage; } @Override public void run() { BulletinBoard transitBoard = boardRegistry.getTransitBoard(transitBoardMessage.getOperationNumber(), transitBoardMessage.getBoardName(), transitBoardMessage.getStepNumber()); BulletinBoard homeBoard = boardRegistry.getBoard(transitBoardMessage.getBoardName()); PostedMessageBlock.Builder messageFetcher = new PostedMessageBlock.Builder(10); homeBoard.clear(); int index = 0; for (PostedMessage postedMessage : transitBoard) { messageFetcher.add(index++, postedMessage.getMessage()); if (messageFetcher.isFull()) { homeBoard.postMessageBlock(messageFetcher.build()); messageFetcher.clear(); } } if (!messageFetcher.isEmpty()) { homeBoard.postMessageBlock(messageFetcher.build()); } boardRegistry.shuffleUnlock(transitBoardMessage.getBoardName()); } } private class TranscriptGenerator { private CMSSignedDataStreamGenerator cmsGen = new CMSSignedDataStreamGenerator(); private ByteArrayOutputStream bOut = new ByteArrayOutputStream(); private volatile OutputStream cmsOut; TranscriptGenerator() throws IOException { } public void writeFragment(byte[] fragment) throws Exception { if (cmsOut == null) { KeyStore nodeCAStore = nodeContext.getNodeCAStore(); Certificate[] nodeCerts = nodeCAStore.getCertificateChain("nodeCA"); cmsGen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").build( "SHA256withECDSA", (PrivateKey) nodeCAStore.getKey("nodeCA", new char[0]), (X509Certificate) nodeCerts[0])); for (Certificate cert : nodeCerts) { cmsGen.addCertificate(new JcaX509CertificateHolder((X509Certificate) cert)); } cmsOut = cmsGen.open(bOut, true); } cmsOut.write(fragment); } public boolean hasData() { return cmsOut != null; } public void finish() throws IOException { cmsOut.close(); cmsOut = null; } public byte[] getFragment() { byte[] fragment = bOut.toByteArray(); bOut.reset(); return fragment; } } }