package net.i2p.crypto; import java.io.EOFException; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; import java.security.DigestInputStream; import java.security.DigestOutputStream; import java.security.GeneralSecurityException; import java.security.MessageDigest; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.X509Certificate; import java.security.cert.X509CRL; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.Properties; import gnu.getopt.Getopt; import net.i2p.I2PAppContext; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Signature; import net.i2p.data.SimpleDataStructure; import net.i2p.util.SecureFileOutputStream; /** * Succesor to the ".sud" format used in TrustedUpdate. * Format specified in http://www.i2p2.de/updates * * @since 0.9.8 */ public class SU3File { private final I2PAppContext _context; private final File _file; private String _version; private int _versionLength; private String _signer; private int _signatureLength; private int _signerLength; private int _fileType = -1; private ContentType _contentType; private long _contentLength; private PublicKey _signerPubkey; private boolean _headerVerified; private SigType _sigType; private boolean _verifySignature = true; private File _certFile; public static final String MAGIC = "I2Psu3"; private static final byte[] MAGIC_BYTES = DataHelper.getASCII(MAGIC); private static final int FILE_VERSION = 0; private static final int MIN_VERSION_BYTES = 16; private static final int VERSION_OFFSET = 40; // Signature.SIGNATURE_BYTES; avoid early ctx init /** * The file type is advisory and is application-dependent. * The following values are defined but any value 0-255 is allowed. */ public static final int TYPE_ZIP = 0; /** @since 0.9.15 */ public static final int TYPE_XML = 1; /** @since 0.9.15 */ public static final int TYPE_HTML = 2; /** @since 0.9.17 */ public static final int TYPE_XML_GZ = 3; /** @since 0.9.28 */ public static final int TYPE_TXT_GZ = 4; public static final int CONTENT_UNKNOWN = 0; public static final int CONTENT_ROUTER = 1; public static final int CONTENT_PLUGIN = 2; public static final int CONTENT_RESEED = 3; /** @since 0.9.15 */ public static final int CONTENT_NEWS = 4; /** @since 0.9.28 */ public static final int CONTENT_BLOCKLIST = 5; /** * The ContentType is the trust domain for the content. * The signer and signature will be checked with the * trusted certificates for that type. */ private enum ContentType { UNKNOWN(CONTENT_UNKNOWN, "unknown"), ROUTER(CONTENT_ROUTER, "router"), PLUGIN(CONTENT_PLUGIN, "plugin"), RESEED(CONTENT_RESEED, "reseed"), NEWS(CONTENT_NEWS, "news"), BLOCKLIST(CONTENT_BLOCKLIST, "blocklist") ; private final int code; private final String name; ContentType(int code, String name) { this.code = code; this.name = name; } public int getCode() { return code; } public String getName() { return name; } /** @return null if not supported */ public static ContentType getByCode(int code) { return BY_CODE.get(Integer.valueOf(code)); } } private static final Map<Integer, ContentType> BY_CODE = new HashMap<Integer, ContentType>(); static { for (ContentType type : ContentType.values()) { BY_CODE.put(Integer.valueOf(type.getCode()), type); } } private static final ContentType DEFAULT_CONTENT_TYPE = ContentType.UNKNOWN; // avoid early ctx init //private static final SigType DEFAULT_SIG_TYPE = SigType.DSA_SHA1; private static final int DEFAULT_SIG_CODE = 6; /** * */ public SU3File(String file) { this(new File(file)); } /** * */ public SU3File(File file) { this(I2PAppContext.getGlobalContext(), file); } /** * */ public SU3File(I2PAppContext context, File file) { _context = context; _file = file; } /** * Should the signature be verified? Default true * @since 0.9.15 */ public void setVerifySignature(boolean shouldVerify) { _verifySignature = shouldVerify; } /** * Use this X.509 cert file for verification instead of $I2P/certificates/content_type/foo_at_mail.i2p * @since 0.9.15 */ private void setPublicKeyCertificate(File certFile) { _certFile = certFile; } /** * This does not check the signature, but it will fail if the signer is unknown, * unless setVerifySignature(false) has been called. */ public String getVersionString() throws IOException { verifyHeader(); return _version; } /** * This does not check the signature, but it will fail if the signer is unknown, * unless setVerifySignature(false) has been called. */ public String getSignerString() throws IOException { verifyHeader(); return _signer; } /** * This does not check the signature, but it will fail if the signer is unknown, * unless setVerifySignature(false) has been called. * * @return null if unknown * @since 0.9.9 */ public SigType getSigType() throws IOException { verifyHeader(); return _sigType; } /** * The ContentType is the trust domain for the content. * The signer and signature will be checked with the * trusted certificates for that type. * * This does not check the signature, but it will fail if the signer is unknown, * unless setVerifySignature(false) has been called. * * @return -1 if unknown * @since 0.9.9 */ public int getContentType() throws IOException { verifyHeader(); return _contentType != null ? _contentType.getCode() : -1; } /** * The file type is advisory and is application-dependent. * The following values are defined but any value 0-255 is allowed. * * This does not check the signature, but it will fail if the signer is unknown, * unless setVerifySignature(false) has been called. * * @return 0-255 or -1 if unknown * @since 0.9.15 */ public int getFileType() throws IOException { verifyHeader(); return _fileType; } /** * This does not check the signature, but it will fail if the signer is unknown, * unless setVerifySignature(false) has been called. * * Throws IOE if verify vails. */ public void verifyHeader() throws IOException { if (_headerVerified) return; InputStream in = null; try { in = new FileInputStream(_file); verifyHeader(in); } catch (DataFormatException dfe) { IOException ioe = new IOException("foo"); ioe.initCause(dfe); throw ioe; } finally { if (in != null) try { in.close(); } catch (IOException ioe) {} } } /** * Throws if verify vails. */ private void verifyHeader(InputStream in) throws IOException, DataFormatException { byte[] magic = new byte[MAGIC_BYTES.length]; DataHelper.read(in, magic); if (!DataHelper.eq(magic, MAGIC_BYTES)) throw new IOException("Not an su3 file"); skip(in, 1); int foo = in.read(); if (foo != FILE_VERSION) throw new IOException("bad file version"); int sigTypeCode = (int) DataHelper.readLong(in, 2); _sigType = SigType.getByCode(sigTypeCode); // In verifyAndMigrate it reads this far then rewinds, but we don't need to here if (_sigType == null) throw new IOException("unknown sig type: " + sigTypeCode); _signatureLength = (int) DataHelper.readLong(in, 2); if (_signatureLength != _sigType.getSigLen()) throw new IOException("bad sig length"); skip(in, 1); int _versionLength = in.read(); if (_versionLength < MIN_VERSION_BYTES) throw new IOException("bad version length"); skip(in, 1); _signerLength = in.read(); if (_signerLength <= 0) throw new IOException("bad signer length"); _contentLength = DataHelper.readLong(in, 8); if (_contentLength <= 0) throw new IOException("bad content length"); skip(in, 1); _fileType = in.read(); // Allow any file type //if (_fileType != TYPE_ZIP && _fileType != TYPE_XML) // throw new IOException("bad file type"); skip(in, 1); int cType = in.read(); _contentType = BY_CODE.get(Integer.valueOf(cType)); if (_contentType == null) throw new IOException("unknown content type " + cType); skip(in, 12); byte[] data = new byte[_versionLength]; int bytesRead = DataHelper.read(in, data); if (bytesRead != _versionLength) throw new EOFException(); int zbyte; for (zbyte = 0; zbyte < _versionLength; zbyte++) { if (data[zbyte] == 0x00) break; } _version = new String(data, 0, zbyte, "UTF-8"); data = new byte[_signerLength]; bytesRead = DataHelper.read(in, data); if (bytesRead != _signerLength) throw new EOFException(); _signer = DataHelper.getUTF8(data); if (_verifySignature) { if (_certFile != null) { _signerPubkey = loadKey(_certFile); } else { // look in both install dir and config dir for the signer cert KeyRing ring = new DirKeyRing(new File(_context.getBaseDir(), "certificates")); try { _signerPubkey = ring.getKey(_signer, _contentType.getName(), _sigType); } catch (GeneralSecurityException gse) { IOException ioe = new IOException("keystore error"); ioe.initCause(gse); throw ioe; } if (_signerPubkey == null) { boolean diff = true; try { diff = !_context.getBaseDir().getCanonicalPath().equals(_context.getConfigDir().getCanonicalPath()); } catch (IOException ioe) {} if (diff) { ring = new DirKeyRing(new File(_context.getConfigDir(), "certificates")); try { _signerPubkey = ring.getKey(_signer, _contentType.getName(), _sigType); } catch (GeneralSecurityException gse) { IOException ioe = new IOException("keystore error"); ioe.initCause(gse); throw ioe; } } if (_signerPubkey == null) throw new IOException("unknown signer: " + _signer + " for content type: " + _contentType.getName()); } } } _headerVerified = true; } /** skip but update digest */ private static void skip(InputStream in, int cnt) throws IOException { for (int i = 0; i < cnt; i++) { if (in.read() < 0) throw new EOFException(); } } private int getContentOffset() throws IOException { verifyHeader(); return VERSION_OFFSET + _versionLength + _signerLength; } /** * One-pass verify. * Throws IOE on all format errors. * * @return true if signature is good * @since 0.9.9 */ public boolean verify() throws IOException { return verifyAndMigrate(null); } /** * One-pass verify and extract the content. * Recommend extracting to a temp location as the sig is not checked until * after extraction. This will delete the file if the sig does not verify. * Throws IOE on all format errors. * * @param migrateTo the output file, probably in zip format. Null for verify only. * @return true if signature is good */ public boolean verifyAndMigrate(File migrateTo) throws IOException { InputStream in = null; OutputStream out = null; boolean rv = false; try { in = new BufferedInputStream(new FileInputStream(_file)); // read 10 bytes to get the sig type in.mark(10); // following is a dup of that in verifyHeader() byte[] magic = new byte[MAGIC_BYTES.length]; DataHelper.read(in, magic); if (!DataHelper.eq(magic, MAGIC_BYTES)) throw new IOException("Not an su3 file"); skip(in, 1); int foo = in.read(); if (foo != FILE_VERSION) throw new IOException("bad file version"); skip(in, 1); int sigTypeCode = in.read(); _sigType = SigType.getByCode(sigTypeCode); if (_sigType == null) throw new IOException("unknown sig type: " + sigTypeCode); // end duplicate code // rewind in.reset(); MessageDigest md = _sigType.getDigestInstance(); DigestInputStream din = new DigestInputStream(in, md); in = din; if (!_headerVerified) verifyHeader(in); else skip(in, getContentOffset()); if (_verifySignature) { if (_signerPubkey == null) throw new IOException("unknown signer: " + _signer + " for content type: " + _contentType.getName()); } if (migrateTo != null) // else verify only out = new FileOutputStream(migrateTo); byte[] buf = new byte[16*1024]; long tot = 0; while (tot < _contentLength) { int read = in.read(buf, 0, (int) Math.min(buf.length, _contentLength - tot)); if (read < 0) throw new EOFException(); if (migrateTo != null) // else verify only out.write(buf, 0, read); tot += read; } if (_verifySignature) { byte[] sha = md.digest(); din.on(false); Signature signature = new Signature(_sigType); signature.readBytes(in); int avail = in.available(); if (avail > 0) throw new IOException(avail + " bytes data after sig"); SimpleDataStructure hash = _sigType.getHashInstance(); hash.setData(sha); //System.out.println("hash\n" + HexDump.dump(sha)); //System.out.println("sig\n" + HexDump.dump(signature.getData())); rv = _context.dsa().verifySignature(signature, hash, _signerPubkey); } else { rv = true; } } catch (DataFormatException dfe) { IOException ioe = new IOException("foo"); ioe.initCause(dfe); throw ioe; } finally { if (in != null) try { in.close(); } catch (IOException ioe) {} if (out != null) try { out.close(); } catch (IOException ioe) {} if (migrateTo != null && !rv) migrateTo.delete(); } return rv; } /** * One-pass wrap and sign the content. * Writes to the file specified in the constructor. * Throws on all errors. * * @param content the input file, probably in zip format * @param fileType 0-255, 0 for zip * @param contentType 0-255 * @param version 1-255 bytes when converted to UTF-8 * @param signer ID of the public key, 1-255 bytes when converted to UTF-8 */ public void write(File content, int fileType, int contentType, String version, String signer, PrivateKey privkey, SigType sigType) throws IOException { InputStream in = null; DigestOutputStream out = null; boolean ok = false; try { in = new BufferedInputStream(new FileInputStream(content)); MessageDigest md = sigType.getDigestInstance(); out = new DigestOutputStream(new BufferedOutputStream(new FileOutputStream(_file)), md); out.write(MAGIC_BYTES); out.write((byte) 0); out.write((byte) FILE_VERSION); DataHelper.writeLong(out, 2, sigType.getCode()); DataHelper.writeLong(out, 2, sigType.getSigLen()); out.write((byte) 0); byte[] verBytes = DataHelper.getUTF8(version); if (verBytes.length == 0 || verBytes.length > 255) throw new IllegalArgumentException("bad version length"); int verLen = Math.max(verBytes.length, MIN_VERSION_BYTES); out.write((byte) verLen); out.write((byte) 0); byte[] signerBytes = DataHelper.getUTF8(signer); if (signerBytes.length == 0 || signerBytes.length > 255) throw new IllegalArgumentException("bad signer length"); out.write((byte) signerBytes.length); long contentLength = content.length(); if (contentLength <= 0) throw new IllegalArgumentException("No content"); DataHelper.writeLong(out, 8, contentLength); out.write((byte) 0); if (fileType < 0 || fileType > 255) throw new IllegalArgumentException("bad content type"); out.write((byte) fileType); out.write((byte) 0); if (contentType < 0 || contentType > 255) throw new IllegalArgumentException("bad content type"); out.write((byte) contentType); out.write(new byte[12]); out.write(verBytes); if (verBytes.length < MIN_VERSION_BYTES) out.write(new byte[MIN_VERSION_BYTES - verBytes.length]); out.write(signerBytes); byte[] buf = new byte[16*1024]; long tot = 0; while (tot < contentLength) { int read = in.read(buf, 0, (int) Math.min(buf.length, contentLength - tot)); if (read < 0) throw new EOFException(); out.write(buf, 0, read); tot += read; } byte[] sha = md.digest(); out.on(false); SimpleDataStructure hash = sigType.getHashInstance(); hash.setData(sha); Signature signature = _context.dsa().sign(hash, privkey, sigType); if (signature == null) throw new IOException("sig fail"); //System.out.println("hash\n" + HexDump.dump(sha)); //System.out.println("sig\n" + HexDump.dump(signature.getData())); signature.writeBytes(out); ok = true; } catch (DataFormatException dfe) { IOException ioe = new IOException("foo"); ioe.initCause(dfe); throw ioe; } finally { if (in != null) try { in.close(); } catch (IOException ioe) {} if (out != null) try { out.close(); } catch (IOException ioe) {} if (!ok) _file.delete(); } } /** * Parses command line arguments when this class is used from the command * line. * Exits 1 on failure so this can be used in scripts. * * @param args Command line parameters. */ public static void main(String[] args) { boolean ok = false; try { // defaults String stype = null; String ctype = null; String ftype = null; String kfile = null; String crlfile = null; String kspass = KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD; boolean error = false; boolean shouldVerify = true; Getopt g = new Getopt("SU3File", args, "t:c:f:k:xp:r:"); int c; while ((c = g.getopt()) != -1) { switch (c) { case 't': stype = g.getOptarg(); break; case 'c': ctype = g.getOptarg(); break; case 'f': ftype = g.getOptarg(); break; case 'k': kfile = g.getOptarg(); break; case 'r': crlfile = g.getOptarg(); break; case 'x': shouldVerify = false; break; case 'p': kspass = g.getOptarg(); break; case '?': case ':': default: error = true; } } int idx = g.getOptind(); String cmd = args[idx]; List<String> a = new ArrayList<String>(Arrays.asList(args).subList(idx + 1, args.length)); if (error) { showUsageCLI(); } else if ("showversion".equals(cmd)) { ok = showVersionCLI(a.get(0)); } else if ("sign".equals(cmd)) { // speed things up by specifying a small PRNG buffer size Properties props = new Properties(); props.setProperty("prng.bufferSize", "16384"); new I2PAppContext(props); ok = signCLI(stype, ctype, ftype, a.get(0), a.get(1), a.get(2), a.get(3), a.get(4), "", kspass); } else if ("bulksign".equals(cmd)) { Properties props = new Properties(); props.setProperty("prng.bufferSize", "16384"); new I2PAppContext(props); ok = bulkSignCLI(stype, ctype, a.get(0), a.get(1), a.get(2), a.get(3), kspass); } else if ("verifysig".equals(cmd)) { ok = verifySigCLI(a.get(0), kfile); } else if ("keygen".equals(cmd)) { Properties props = new Properties(); props.setProperty("prng.bufferSize", "16384"); new I2PAppContext(props); ok = genKeysCLI(stype, a.get(0), a.get(1), crlfile, a.get(2), kspass); } else if ("extract".equals(cmd)) { ok = extractCLI(a.get(0), a.get(1), shouldVerify, kfile); } else { showUsageCLI(); } } catch (NoSuchElementException nsee) { showUsageCLI(); } catch (IndexOutOfBoundsException ioobe) { showUsageCLI(); } if (!ok) System.exit(1); } private static final void showUsageCLI() { System.err.println("Usage: SU3File keygen [-t type|code] [-p keystorepw] [-r crlFile.crl] publicKeyFile.crt keystore.ks you@mail.i2p\n" + " SU3File sign [-t type|code] [-c type|code] [-f type|code] [-p keystorepw] inputFile.zip signedFile.su3 keystore.ks version you@mail.i2p\n" + " SU3File bulksign [-t type|code] [-c type|code] [-p keystorepw] directory keystore.ks version you@mail.i2p\n" + " (signs all .zip, .xml, and .xml.gz files in the directory)\n" + " SU3File showversion signedFile.su3\n" + " SU3File verifysig [-k file.crt] signedFile.su3 ## -k use this pubkey cert for verification\n" + " SU3File extract [-x] [-k file.crt] signedFile.su3 outFile ## -x don't check sig"); System.err.println("Default keystore password: \"" + KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD + '"'); System.err.println(dumpTypes()); } /** @since 0.9.9 */ private static String dumpTypes() { StringBuilder buf = new StringBuilder(256); buf.append("Available signature types (-t):\n"); for (SigType t : EnumSet.allOf(SigType.class)) { if (!t.isAvailable()) continue; if (t == SigType.EdDSA_SHA512_Ed25519) continue; // not supported by keytool, and does double hashing right now buf.append(" ").append(t).append("\t(code: ").append(t.getCode()).append(')'); if (t.getCode() == DEFAULT_SIG_CODE) buf.append(" DEFAULT"); if (!t.isAvailable()) buf.append(" UNAVAILABLE"); buf.append('\n'); } buf.append("Available content types (-c):\n"); for (ContentType t : EnumSet.allOf(ContentType.class)) { buf.append(" ").append(t).append("\t(code: ").append(t.getCode()).append(')'); if (t == DEFAULT_CONTENT_TYPE) buf.append(" DEFAULT"); buf.append('\n'); } buf.append("Available file types (-f):\n" + " ZIP\t(code: 0) DEFAULT\n" + " XML\t(code: 1)\n" + " HTML\t(code: 2)\n" + " XML_GZ\t(code: 3)\n" + " TXT_GZ\t(code: 4)\n" + " (user defined)\t(code: 5-255)\n"); return buf.toString(); } /** * @param stype number or name * @return null if not found * @since 0.9.9 */ private static ContentType parseContentType(String ctype) { try { return ContentType.valueOf(ctype.toUpperCase(Locale.US)); } catch (IllegalArgumentException iae) { try { int code = Integer.parseInt(ctype); return ContentType.getByCode(code); } catch (NumberFormatException nfe) { return null; } } } /** @return success */ private static final boolean showVersionCLI(String signedFile) { try { SU3File file = new SU3File(signedFile); file.setVerifySignature(false); String versionString = file.getVersionString(); if (versionString.equals("")) System.out.println("No version string found in file '" + signedFile + "'"); else System.out.println("Version: " + versionString); String signerString = file.getSignerString(); if (signerString.equals("")) System.out.println("No signer string found in file '" + signedFile + "'"); else System.out.println("Signer: " + signerString); if (file._sigType != null) System.out.println("SigType: " + file._sigType); if (file._contentType != null) System.out.println("Content: " + file._contentType); String ftype; if (file._fileType == TYPE_ZIP) ftype = "ZIP"; else if (file._fileType == TYPE_XML) ftype = "XML"; else if (file._fileType == TYPE_HTML) ftype = "HTML"; else if (file._fileType == TYPE_XML_GZ) ftype = "XML_GZ"; else ftype = Integer.toString(file._fileType); System.out.println("FileType: " + ftype); return !versionString.equals(""); } catch (IOException ioe) { ioe.printStackTrace(); return false; } } /** * Zip, xml, and xml.gz only * @return success * @since 0.9.9 */ private static final boolean bulkSignCLI(String stype, String ctype, String dir, String privateKeyFile, String version, String signerName, String kspass) { File d = new File(dir); if (!d.isDirectory()) { System.out.println("Directory does not exist: " + d); return false; } File[] files = d.listFiles(); if (files == null || files.length == 0) { System.out.println("No zip files found in " + d); return false; } String keypw = ""; try { while (keypw.length() < 6) { System.out.print("Enter password for key \"" + signerName + "\": "); keypw = DataHelper.readLine(System.in); if (keypw == null) { System.out.println("\nEOF reading password"); return false; } keypw = keypw.trim(); if (keypw.length() > 0 && keypw.length() < 6) System.out.println("Key password must be at least 6 characters"); } } catch (IOException ioe) { System.out.println("Error asking for password"); ioe.printStackTrace(); return false; } int success = 0; for (File in : files) { String inputFile = in.getPath(); int len; String ftype; if (inputFile.endsWith(".zip")) { len = 4; ftype = "ZIP"; } else if (inputFile.endsWith(".xml")) { len = 4; ftype = "XML"; } else if (inputFile.endsWith(".xml.gz")) { len = 7; ftype = "XML_GZ"; } else { continue; } String signedFile = inputFile.substring(0, inputFile.length() - len) + ".su3"; boolean rv = signCLI(stype, ctype, ftype, inputFile, signedFile, privateKeyFile, version, signerName, keypw, kspass); if (!rv) return false; success++; } if (success == 0) System.out.println("No files processed in " + d); return success > 0; } /** * @return success * @since 0.9.9 */ private static final boolean signCLI(String stype, String ctype, String ftype, String inputFile, String signedFile, String privateKeyFile, String version, String signerName, String keypw, String kspass) { SigType type = stype == null ? SigType.getByCode(Integer.valueOf(DEFAULT_SIG_CODE)) : SigType.parseSigType(stype); if (type == null) { System.out.println("Signature type " + stype + " is not supported"); return false; } ContentType ct = ctype == null ? DEFAULT_CONTENT_TYPE : parseContentType(ctype); if (ct == null) { System.out.println("Content type " + ctype + " is not supported"); return false; } int ft = TYPE_ZIP; if (ftype != null) { if (ftype.equalsIgnoreCase("ZIP")) { ft = TYPE_ZIP; } else if (ftype.equalsIgnoreCase("XML")) { ft = TYPE_XML; } else if (ftype.equalsIgnoreCase("HTML")) { ft = TYPE_HTML; } else if (ftype.equalsIgnoreCase("XML_GZ")) { ft = TYPE_XML_GZ; } else { try { ft = Integer.parseInt(ftype); } catch (NumberFormatException nfe) { ft = -1; } if (ft < 0 || ft > 255) { System.out.println("File type " + ftype + " is not supported"); return false; } if (ft > TYPE_XML_GZ) System.out.println("Warning: File type " + ftype + " is undefined"); } } return signCLI(type, ct, ft, inputFile, signedFile, privateKeyFile, version, signerName, keypw, kspass); } /** * @return success * @since 0.9.9 */ private static final boolean signCLI(SigType type, ContentType ctype, int ftype, String inputFile, String signedFile, String privateKeyFile, String version, String signerName, String keypw, String kspass) { try { while (keypw.length() < 6) { System.out.print("Enter password for key \"" + signerName + "\": "); keypw = DataHelper.readLine(System.in); if (keypw == null) { System.out.println("\nEOF reading password"); return false; } keypw = keypw.trim(); if (keypw.length() > 0 && keypw.length() < 6) System.out.println("Key password must be at least 6 characters"); } File pkfile = new File(privateKeyFile); PrivateKey pk = KeyStoreUtil.getPrivateKey(pkfile, kspass, signerName, keypw); if (pk == null) { System.out.println("Private key for " + signerName + " not found in keystore " + privateKeyFile); return false; } // now fix the sig type based on the private key SigType oldType = type; type = SigUtil.fromJavaKey(pk).getType(); if (oldType != type) System.out.println("Warning: Using private key type " + type + ", ignoring specified type " + oldType); SU3File file = new SU3File(signedFile); file.write(new File(inputFile), ftype, ctype.getCode(), version, signerName, pk, type); System.out.println("Input file '" + inputFile + "' signed and written to '" + signedFile + "'"); return true; } catch (GeneralSecurityException gse) { System.out.println("Error signing input file '" + inputFile + "'"); gse.printStackTrace(); return false; } catch (IOException ioe) { System.out.println("Error signing input file '" + inputFile + "'"); ioe.printStackTrace(); return false; } } /** @return valid */ private static final boolean verifySigCLI(String signedFile, String pkFile) { InputStream in = null; try { SU3File file = new SU3File(signedFile); if (pkFile != null) file.setPublicKeyCertificate(new File(pkFile)); boolean isValidSignature = file.verify(); if (isValidSignature) System.out.println("Signature VALID (signed by " + file.getSignerString() + ' ' + file._sigType + ')'); else System.out.println("Signature INVALID (signed by " + file.getSignerString() + ' ' + file._sigType +')'); return isValidSignature; } catch (IOException ioe) { System.out.println("Error verifying input file '" + signedFile + "'"); ioe.printStackTrace(); return false; } } /** * @return success * @since 0.9.9 */ private static final boolean extractCLI(String signedFile, String outFile, boolean verifySig, String pkFile) { InputStream in = null; try { SU3File file = new SU3File(signedFile); if (pkFile != null) file.setPublicKeyCertificate(new File(pkFile)); file.setVerifySignature(verifySig); File out = new File(outFile); boolean ok = file.verifyAndMigrate(out); if (ok) System.out.println("File extracted (signed by " + file.getSignerString() + ' ' + file._sigType + ')'); else System.out.println("Signature INVALID (signed by " + file.getSignerString() + ' ' + file._sigType +')'); return ok; } catch (IOException ioe) { System.out.println("Error extracting from file '" + signedFile + "'"); ioe.printStackTrace(); return false; } } /** * @param crlFile may be null; non-null to save * @return success * @since 0.9.9 */ private static final boolean genKeysCLI(String stype, String publicKeyFile, String privateKeyFile, String crlFile, String alias, String kspass) { SigType type = stype == null ? SigType.getByCode(Integer.valueOf(DEFAULT_SIG_CODE)) : SigType.parseSigType(stype); if (type == null) { System.out.println("Signature type " + stype + " is not supported"); return false; } return genKeysCLI(type, publicKeyFile, privateKeyFile, crlFile, alias, kspass); } /** * Writes Java-encoded keys (X.509 for public and PKCS#8 for private) * * @param crlFile may be null; non-null to save * @return success * @since 0.9.9 */ private static final boolean genKeysCLI(SigType type, String publicKeyFile, String privateKeyFile, String crlFile, String alias, String kspass) { File pubFile = new File(publicKeyFile); if (pubFile.exists()) { System.out.println("Error: Not overwriting file " + publicKeyFile); return false; } File ksFile = new File(privateKeyFile); String keypw = ""; try { while (alias.length() == 0) { System.out.print("Enter key name (example@mail.i2p): "); alias = DataHelper.readLine(System.in); if (alias == null) { System.out.println("\nEOF reading key name"); return false; } alias = alias.trim(); } while (keypw.length() < 6) { System.out.print("Enter new key password: "); keypw = DataHelper.readLine(System.in); if (keypw == null) { System.out.println("\nEOF reading password"); return false; } keypw = keypw.trim(); if (keypw.length() > 0 && keypw.length() < 6) System.out.println("Key password must be at least 6 characters"); } } catch (IOException ioe) { return false; } OutputStream out = null; try { Object[] rv = KeyStoreUtil.createKeysAndCRL(ksFile, kspass, alias, alias, "I2P", 3652, type, keypw); X509Certificate cert = (X509Certificate) rv[2]; out = new SecureFileOutputStream(publicKeyFile); CertUtil.exportCert(cert, out); if (crlFile != null) { out.close(); X509CRL crl = (X509CRL) rv[3]; out = new SecureFileOutputStream(crlFile); CertUtil.exportCRL(crl, out); } } catch (GeneralSecurityException gse) { System.err.println("Error creating keys for " + alias); gse.printStackTrace(); return false; } catch (IOException ioe) { System.err.println("Error creating keys for " + alias); ioe.printStackTrace(); return false; } finally { if (out != null) try { out.close(); } catch (IOException ioe) {} } return true; } /** * For the -k CLI option * @return non-null, throws IOE on all errors * @since 0.9.15 */ private static PublicKey loadKey(File kd) throws IOException { try { return CertUtil.loadKey(kd); } catch (GeneralSecurityException gse) { IOException ioe = new IOException("cert error"); ioe.initCause(gse); throw ioe; } } }