/* * Copyright 2011 Christian Thiemann <christian@spato.net> * Developed at Northwestern University <http://rocs.northwestern.edu> * * This file is part of the deployment infrastructure for * SPaTo Visual Explorer (SPaTo). * * SPaTo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * SPaTo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with SPaTo. If not, see <http://www.gnu.org/licenses/>. */ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.PrintWriter; import java.security.KeyStore; import java.security.MessageDigest; import java.security.PrivateKey; import java.security.Signature; import javax.swing.JOptionPane; import javax.swing.JPasswordField; import net.spato.sve.app.SPaTo_Visual_Explorer; import net.spato.sve.app.util.Base64; import processing.xml.XMLElement; public class SPaTo_Update_Builder { private static String fixPath(String path) { return path.replace('/', File.separatorChar).replace('\\', File.separatorChar); } private static PrivateKey getPrivateKey(String keystore, String alias, String password) throws Exception { char pass[] = null; // get password if ((password != null) && (password.length() > 0) && !password.equals("${private.key.password}")) pass = password.toCharArray(); else { JPasswordField pw = new JPasswordField(30); int res = JOptionPane.showConfirmDialog(null, pw, "Enter private key password", JOptionPane.OK_CANCEL_OPTION); pass = pw.getPassword(); if ((res != JOptionPane.OK_OPTION) || (pass == null) || (pass.length == 0)) throw new Exception("No password provided!"); } // get key FileInputStream fis = new FileInputStream(keystore); KeyStore store = KeyStore.getInstance("JKS"); store.load(fis, pass); fis.close(); return (PrivateKey)store.getKey(alias, pass); } private static XMLElement getReleaseInfo(String releaseNotes) throws Exception { XMLElement xmlRelease = loadXML(releaseNotes).getChild(0); while (xmlRelease.getChildCount() > 0) xmlRelease.removeChild(0); // purge the actual release notes System.out.println(xmlRelease); if (!xmlRelease.getString("version").equals(SPaTo_Visual_Explorer.VERSION)) throw new Exception("Version mismatch: " + xmlRelease.getString("version") + " != " + SPaTo_Visual_Explorer.VERSION); if (!xmlRelease.getString("debug").equals(SPaTo_Visual_Explorer.VERSION_DEBUG)) throw new Exception("Version debug mismatch: " + xmlRelease.getString("debug") + " != " + SPaTo_Visual_Explorer.VERSION_DEBUG); if (!xmlRelease.getString("date").equals(SPaTo_Visual_Explorer.VERSION_TIMESTAMP)) throw new Exception("Version date mismatch: " + xmlRelease.getString("date") + " != " + SPaTo_Visual_Explorer.VERSION_TIMESTAMP); return xmlRelease; } private static XMLElement loadXML(String filename) throws Exception { FileReader reader = new FileReader(filename); XMLElement xml = XMLElement.parse(reader); reader.close(); if (xml == null) throw new Exception("XMLElement.parse returned null for " + filename); return xml; } private static void saveXML(XMLElement xml, String filename) throws Exception { PrintWriter writer = new PrintWriter(filename); if (!xml.write(writer)) throw new Exception("XMLElement.write returned false writing to " + filename); writer.close(); } public static String serializeDigest(byte digest[]) { return String.format("%032x", new java.math.BigInteger(1, digest)); } public static XMLElement serializeSignature(byte signature[]) { XMLElement xmlSignature = new XMLElement("signature"); String sigStr = ""; for (String line : Base64.encode(signature, 43).split("\r\n")) sigStr += " " + line + "\n"; xmlSignature.setContent("\n" + sigStr + " "); return xmlSignature; } public static void processIndex(String filename, String srcdir, String dstdir, XMLElement xmlRelease, PrivateKey privKey) throws Exception { // load index and add release info XMLElement xmlIndex = loadXML(filename); xmlIndex.insertChild(xmlRelease, 0); System.out.println("Processing " + xmlIndex.getString("index") + " (" + xmlIndex.getChildren("file").length + " files)"); // set up crypto-stuff byte buf[] = new byte[8*1024]; MessageDigest md5 = MessageDigest.getInstance("md5"); Signature sig = Signature.getInstance("MD5withRSA"); sig.initSign(privKey); // copy files to update directory (dstdir) and add MD5 checksums and signatures to index for (XMLElement xmlFile : xmlIndex.getChildren("file")) { File fsrc = new File(srcdir, fixPath(xmlFile.getChild("local").getString("path"))); File fdst = new File(dstdir, fixPath(xmlFile.getChild("remote").getString("path"))); fdst.getParentFile().mkdirs(); // make sure the destination directory exists // feed the file contents through MD5 and RSA while copying... FileInputStream fis = new FileInputStream(fsrc); FileOutputStream fos = new FileOutputStream(fdst); int n, size = 0; while ((n = fis.read(buf)) > 0) { size += n; md5.update(buf, 0, n); sig.update(buf, 0, n); fos.write(buf, 0, n); } fis.close(); fos.close(); // copy last-modified timestamp fdst.setLastModified(fsrc.lastModified()); // add file size, digest and signature to the index xmlFile.getChild("remote").setString("size", "" + size); xmlFile.getChild("remote").setString("md5", serializeDigest(md5.digest())); xmlFile.addChild(serializeSignature(sig.sign())); } // save index file saveXML(xmlIndex, dstdir + File.separator + xmlIndex.getString("index")); } public static void main(String args[]) { try { // load private key and release info PrivateKey privKey = getPrivateKey(fixPath("deploy/keystore"), "spato.update", (args.length > 0) ? args[0] : null); XMLElement xmlRelease = getReleaseInfo(fixPath("docs/release-notes/RELEASE_NOTES.xml")); // copy files into place and add crypto-stuff to indices String dstdir = fixPath("build/update"); processIndex(fixPath("deploy/INDEX.linux"), fixPath("build/linux/SPaTo Visual Explorer"), dstdir, xmlRelease, privKey); processIndex(fixPath("deploy/INDEX.macosx"), fixPath("build/macosx/SPaTo Visual Explorer.app"), dstdir, xmlRelease, privKey); processIndex(fixPath("deploy/INDEX.windows"), fixPath("build/windows/SPaTo Visual Explorer"), dstdir, xmlRelease, privKey); } catch (Exception e) { e.printStackTrace(); } } }