diff --git a/apps/routerconsole/jsp/verifyupdate.jsp b/apps/routerconsole/jsp/verifyupdate.jsp new file mode 100644 index 0000000000000000000000000000000000000000..2d13d3508d9cf2b0c99213132fe4f8bb171edb6e --- /dev/null +++ b/apps/routerconsole/jsp/verifyupdate.jsp @@ -0,0 +1,12 @@ +<%@page contentType="text/html" %> +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> + +<html><head> +<title>I2P Router Console - verify update file signature</title> +</head> +<body> + +<!-- net.i2p.crypto.TrustedUpdate.verify(request.getParameter("filename")) --> + +</body> +</html> diff --git a/core/java/src/net/i2p/crypto/TrustedUpdate.java b/core/java/src/net/i2p/crypto/TrustedUpdate.java new file mode 100644 index 0000000000000000000000000000000000000000..55120c7b7a031e0d28e2a33ae31675f9e7a77e19 --- /dev/null +++ b/core/java/src/net/i2p/crypto/TrustedUpdate.java @@ -0,0 +1,154 @@ +package net.i2p.crypto; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +import net.i2p.I2PAppContext; +import net.i2p.data.Signature; +import net.i2p.data.SigningPrivateKey; +import net.i2p.data.SigningPublicKey; +import net.i2p.util.Log; + +/** + * Handles DSA signing and verification of I2P update archives. + * + * @author smeghead + */ +public class TrustedUpdate { + + private static byte[] I2P_PUBLICKEY = { 'p', 'k' }; + + private I2PAppContext _context; + private Log _log; + + public TrustedUpdate() { + _context = I2PAppContext.getGlobalContext(); + _log = _context.logManager().getLog(TrustedUpdate.class); + } + + public static void main(String[] args) { + // If no context is defined, don't define one. + // Expose verify(inputFile, publicKeyFile) via cli param + } + + /** + * Reads the version string from a signed I2P update file. + * + * @param inputFile A signed I2P update file. + * + * @return The update version string read, or an empty string if no version + * string is present. + */ + public String getUpdateVersion(String inputFile) { + String updateVersion = null; + byte[] data = readFileBytes(inputFile, 0, 16); + try { + updateVersion = new String(data, "UTF-8"); + } catch (UnsupportedEncodingException e) { + // If this ever gets called, you need a new JVM. + } + return updateVersion; + } + + /** + * Uses the given private key to sign the given input file with DSA. The + * output will be a binary file where the first 16 bytes are the I2P + * update's version string encoded in UTF-8 (padded with trailing + * <code>0h</code> characters if necessary), the next 40 bytes are the + * resulting DSA signature, and the remaining bytes are the input file. + * + * @param inputFile The file to be signed. + * @param outputFile The signed file to write. + * @param privateKeyFile The name of the file containing the private key to + * sign <code>inputFile</code> with. + * @param updateVersion The version number of the I2P update. If this + * string is longer than 16 characters it will be + * truncated. + * + * @return An instance of {@link net.i2p.data.Signature}. + */ + public Signature sign(String inputFile, String outputFile, String privateKeyFile, String updateVersion) { + byte[] headerUpdateVersion = { + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 }; + byte[] updateVersionBytes = {}; + if (updateVersion.length() > 16) + updateVersion = updateVersion.substring(0, 16); + try { + updateVersionBytes = updateVersion.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + // If this ever gets called, you need a new JVM. + } + for (int i = 0; i < updateVersionBytes.length; i++) + headerUpdateVersion[i] = updateVersionBytes[i]; + byte[] data = readFileBytes(inputFile, 0, (int) new File(inputFile).length()); + Signature signature = DSAEngine.getInstance().sign(data, new SigningPrivateKey(readFileBytes(privateKeyFile, 0, (int) new File(privateKeyFile).length()-1))); + FileOutputStream fileOutputStream = null; + try { + fileOutputStream = new FileOutputStream(outputFile); + fileOutputStream.write(headerUpdateVersion); + fileOutputStream.write(signature.getData()); + fileOutputStream.write(data); + fileOutputStream.close(); + } catch (IOException ioe) { + if (_log.shouldLog(Log.WARN)) + _log.log(Log.WARN, "Error writing signed I2P update file " + outputFile, ioe); + } + + return signature; + } + + /** + * Verifies the DSA signature of a signed I2P update. + * + * @param inputFile The signed update file to check. + * + * @return <code>true</code> if the file has a valid signature. + */ + public boolean verify(String inputFile) { + DSAEngine.getInstance().verifySignature(new Signature(readFileBytes(inputFile, 16, 55)), + readFileBytes(inputFile, 56, (int) new File(inputFile).length()-57), + new SigningPublicKey(I2P_PUBLICKEY)); + return false; + } + + /** + * Verifies the DSA signature of a signed I2P update. + * + * @param inputFile The signed update file to check. + * @param publicKeyFile The public key to use for verification. + * + * @return <code>true</code> if the file has a valid signature. + */ + public boolean verify(String inputFile, String publicKeyFile) { + DSAEngine.getInstance().verifySignature(new Signature(readFileBytes(inputFile, 16, 55)), + readFileBytes(inputFile, 56, (int) new File(inputFile).length()-57), + new SigningPublicKey(readFileBytes(publicKeyFile, 0, (int) new File(publicKeyFile).length()-1))); + return false; + } + + private byte[] readFileBytes(String inputFile, int offset, int length) { + byte[] bytes = new byte[length]; + FileInputStream fileInputStream = null; + + try { + fileInputStream = new FileInputStream(inputFile); + fileInputStream.read(bytes, offset, length); + fileInputStream.close(); + } catch (FileNotFoundException fnfe) { + if (_log.shouldLog(Log.WARN)) + _log.log(Log.WARN, "File " + inputFile + " not found", fnfe); + } catch (IOException ioe) { + if (_log.shouldLog(Log.WARN)) + _log.log(Log.WARN, "Error reading file " + inputFile, ioe); + } + + return bytes; + } +}