package net.i2p.client.datagram; /* * free (adj.): unencumbered; not under the control of others * Written by human in 2004 and released into the public domain * with no warranty of any kind, either expressed or implied. * It probably won't make your computer catch on fire, or eat * your children, but it might. Use at your own risk. * */ import java.io.ByteArrayInputStream; import java.io.IOException; import net.i2p.I2PAppContext; import net.i2p.crypto.DSAEngine; import net.i2p.crypto.SHA256Generator; import net.i2p.crypto.SigType; import net.i2p.data.DataFormatException; import net.i2p.data.Destination; import net.i2p.data.Hash; import net.i2p.data.Signature; import net.i2p.data.SigningPublicKey; import net.i2p.util.Log; /** * Class for dissecting I2P repliable datagrams, checking the authenticity of * the sender. Note that objects of this class are NOT THREAD SAFE! * * @author human */ public final class I2PDatagramDissector { private static final int DGRAM_BUFSIZE = 32768; private static final int MIN_DGRAM_SIZE = 387 + 40; private final DSAEngine dsaEng = DSAEngine.getInstance(); private final SHA256Generator hashGen = SHA256Generator.getInstance(); private byte[] rxHash; private Signature rxSign; private Destination rxDest; private final byte[] rxPayload = new byte[DGRAM_BUFSIZE]; private int rxPayloadLen; private boolean valid; /** * Crate a new I2P repliable datagram dissector. */ public I2PDatagramDissector() { // nop } /** * Load an I2P repliable datagram into the dissector. * Does NOT verify the signature. * * Format is: * <ol> * <li>Destination (387+ bytes) * <li>Signature (40+ bytes, type and length as implied by signing key type in the Destination) * <li>Payload * </ol> * * For DSA_SHA1 Destinations, the signature is of the SHA-256 Hash of the payload. * * As of 0.9.14, for non-DSA_SHA1 Destinations, the signature is of the payload itself. * * @param dgram non-null I2P repliable datagram to be loaded * * @throws DataFormatException If there's an error in the datagram format */ public void loadI2PDatagram(byte[] dgram) throws DataFormatException { // set invalid(very important!) this.valid = false; if (dgram.length < MIN_DGRAM_SIZE) throw new DataFormatException("repliable datagram too small: " + dgram.length); ByteArrayInputStream dgStream = new ByteArrayInputStream(dgram); try { // read destination rxDest = Destination.create(dgStream); SigType type = rxDest.getSigningPublicKey().getType(); if (type == null) throw new DataFormatException("unsupported sig type"); rxSign = new Signature(type); // read signature rxSign.readBytes(dgStream); // read payload rxPayloadLen = dgStream.read(rxPayload); // calculate the hash of the payload if (type == SigType.DSA_SHA1) { if (rxHash == null) rxHash = new byte[Hash.HASH_LENGTH]; // non-caching hashGen.calculateHash(rxPayload, 0, rxPayloadLen, rxHash, 0); //assert this.hashGen.calculateHash(this.extractPayload()).equals(this.rxHash); } else { rxHash = null; } } catch (IOException e) { // let the application do the logging //Log log = I2PAppContext.getGlobalContext().logManager().getLog(I2PDatagramDissector.class); //log.error("Error loading datagram", e); throw new DataFormatException("Error loading datagram", e); //} catch(AssertionError e) { // Log log = I2PAppContext.getGlobalContext().logManager().getLog(I2PDatagramDissector.class); // log.error("Assertion failed!", e); } //_log.debug("Datagram payload size: " + rxPayloadLen + "; content:\n" // + HexDump.dump(rxPayload, 0, rxPayloadLen)); } /** * Get the payload carried by an I2P repliable datagram (previously loaded * with the loadI2PDatagram() method), verifying the datagram signature. * * @return A byte array containing the datagram payload * * @throws I2PInvalidDatagramException if the signature verification fails */ public byte[] getPayload() throws I2PInvalidDatagramException { this.verifySignature(); return this.extractPayload(); } /** * Get the sender of an I2P repliable datagram (previously loaded with the * loadI2PDatagram() method), verifying the datagram signature. * * @return The Destination of the I2P repliable datagram sender * * @throws I2PInvalidDatagramException if the signature verification fails */ public Destination getSender() throws I2PInvalidDatagramException { this.verifySignature(); return this.extractSender(); } /** * Extract the hash of the payload of an I2P repliable datagram (previously * loaded with the loadI2PDatagram() method), verifying the datagram * signature. * * As of 0.9.14, for signature types other than DSA_SHA1, this returns null. * * @return The hash of the payload of the I2P repliable datagram * @throws I2PInvalidDatagramException if the signature verification fails */ public Hash getHash() throws I2PInvalidDatagramException { // make sure it has a valid signature this.verifySignature(); return extractHash(); } /** * Extract the payload carried by an I2P repliable datagram (previously * loaded with the loadI2PDatagram() method), without verifying the * datagram signature. * * @return A byte array containing the datagram payload */ public byte[] extractPayload() { byte[] retPayload = new byte[this.rxPayloadLen]; System.arraycopy(this.rxPayload, 0, retPayload, 0, this.rxPayloadLen); return retPayload; } /** * Extract the sender of an I2P repliable datagram (previously loaded with * the loadI2PDatagram() method), without verifying the datagram signature. * * @return The Destination of the I2P repliable datagram sender */ public Destination extractSender() { /**** if (this.rxDest == null) return null; Destination retDest = new Destination(); try { retDest.fromByteArray(this.rxDest.toByteArray()); } catch (DataFormatException e) { Log log = I2PAppContext.getGlobalContext().logManager().getLog(I2PDatagramDissector.class); log.error("Caught DataFormatException", e); return null; } return retDest; ****/ // dests are no longer modifiable return rxDest; } /** * Extract the hash of the payload of an I2P repliable datagram (previously * loaded with the loadI2PDatagram() method), without verifying the datagram * signature. * * As of 0.9.14, for signature types other than DSA_SHA1, this returns null. * * @return The hash of the payload of the I2P repliable datagram */ public Hash extractHash() { if (rxHash == null) return null; // make a copy as we will reuse rxHash byte[] hash = new byte[Hash.HASH_LENGTH]; System.arraycopy(rxHash, 0, hash, 0, Hash.HASH_LENGTH); return new Hash(hash); } /** * Verify the signature of this datagram (previously loaded with the * loadI2PDatagram() method) * @throws I2PInvalidDatagramException if the signature is invalid */ public void verifySignature() throws I2PInvalidDatagramException { // first check if it already got validated if(this.valid) return; if (rxSign == null || rxSign.getData() == null || rxDest == null) throw new I2PInvalidDatagramException("Datagram not yet read"); // now validate SigningPublicKey spk = rxDest.getSigningPublicKey(); SigType type = spk.getType(); if (type == null) throw new I2PInvalidDatagramException("unsupported sig type"); if (type == SigType.DSA_SHA1) { if (!this.dsaEng.verifySignature(rxSign, rxHash, spk)) throw new I2PInvalidDatagramException("Incorrect I2P repliable datagram signature"); } else { if (!this.dsaEng.verifySignature(rxSign, rxPayload, 0, rxPayloadLen, spk)) throw new I2PInvalidDatagramException("Incorrect I2P repliable datagram signature"); } // set validated this.valid = true; } }