diff --git a/core/java/src/net/i2p/crypto/SU3File.java b/core/java/src/net/i2p/crypto/SU3File.java index 8b09338a762e3978f582a0aea88ce8ea512e4047..9c158a48a17b052246b41193e438d1e3c2dd8119 100644 --- a/core/java/src/net/i2p/crypto/SU3File.java +++ b/core/java/src/net/i2p/crypto/SU3File.java @@ -15,6 +15,8 @@ import java.security.GeneralSecurityException; import java.security.MessageDigest; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; @@ -55,6 +57,8 @@ public class SU3File { 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); @@ -130,17 +134,44 @@ public class SU3File { _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 */ @@ -150,6 +181,9 @@ public class SU3File { } /** + * 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 */ @@ -159,6 +193,9 @@ public class SU3File { } /** + * 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.15 */ @@ -168,6 +205,9 @@ public class SU3File { } /** + * 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 { @@ -245,17 +285,22 @@ public class SU3File { throw new EOFException(); _signer = DataHelper.getUTF8(data); - 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 (_verifySignature) { + if (_certFile != null) { + _signerPubkey = loadKey(_certFile); + } else { + 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) + throw new IOException("unknown signer: " + _signer + " for content type: " + _contentType.getName()); + } } - - if (_signerPubkey == null) - throw new IOException("unknown signer: " + _signer + " for content type: " + _contentType.getName()); _headerVerified = true; } @@ -324,8 +369,10 @@ public class SU3File { verifyHeader(in); else skip(in, getContentOffset()); - if (_signerPubkey == null) - throw new IOException("unknown signer: " + _signer + " for content type: " + _contentType.getName()); + 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]; @@ -338,15 +385,19 @@ public class SU3File { out.write(buf, 0, read); tot += read; } - byte[] sha = md.digest(); - din.on(false); - Signature signature = new Signature(_sigType); - signature.readBytes(in); - 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); + if (_verifySignature) { + byte[] sha = md.digest(); + din.on(false); + Signature signature = new Signature(_sigType); + signature.readBytes(in); + 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); @@ -461,8 +512,10 @@ public class SU3File { String stype = null; String ctype = null; String ftype = null; + String kfile = null; boolean error = false; - Getopt g = new Getopt("SU3File", args, "t:c:f:"); + boolean shouldVerify = true; + Getopt g = new Getopt("SU3File", args, "t:c:f:k:x"); int c; while ((c = g.getopt()) != -1) { switch (c) { @@ -478,6 +531,14 @@ public class SU3File { ftype = g.getOptarg(); break; + case 'k': + kfile = g.getOptarg(); + break; + + case 'x': + shouldVerify = false; + break; + case '?': case ':': default: @@ -505,11 +566,11 @@ public class SU3File { new I2PAppContext(props); ok = bulkSignCLI(stype, ctype, a.get(0), a.get(1), a.get(2), a.get(3)); } else if ("verifysig".equals(cmd)) { - ok = verifySigCLI(a.get(0)); + ok = verifySigCLI(a.get(0), kfile); } else if ("keygen".equals(cmd)) { ok = genKeysCLI(stype, a.get(0), a.get(1), a.get(2)); } else if ("extract".equals(cmd)) { - ok = extractCLI(a.get(0), a.get(1)); + ok = extractCLI(a.get(0), a.get(1), shouldVerify); } else { showUsageCLI(); } @@ -527,8 +588,8 @@ public class SU3File { System.err.println(" SU3File sign [-t type|code] [-c type|code] [-f type|code] inputFile.zip signedFile.su3 keystore.ks version you@mail.i2p"); System.err.println(" SU3File bulksign [-t type|code] [-c type|code] directory keystore.ks version you@mail.i2p"); System.err.println(" SU3File showversion signedFile.su3"); - System.err.println(" SU3File verifysig signedFile.su3"); - System.err.println(" SU3File extract signedFile.su3 outFile"); + System.err.println(" SU3File verifysig [-k file.crt] signedFile.su3 ## -k use this pubkey cert for verification"); + System.err.println(" SU3File extract [-x] [-k file.crt] signedFile.su3 outFile ## -x don't check sig"); System.err.println(dumpTypes()); } @@ -579,6 +640,7 @@ public class SU3File { 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 + "'"); @@ -726,10 +788,12 @@ public class SU3File { } /** @return valid */ - private static final boolean verifySigCLI(String signedFile) { + 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 + ')'); @@ -747,10 +811,11 @@ public class SU3File { * @return success * @since 0.9.9 */ - private static final boolean extractCLI(String signedFile, String outFile) { + private static final boolean extractCLI(String signedFile, String outFile, boolean verifySig) { InputStream in = null; try { SU3File file = new SU3File(signedFile); + file.setVerifySignature(verifySig); File out = new File(outFile); boolean ok = file.verifyAndMigrate(out); if (ok) @@ -836,4 +901,26 @@ public class SU3File { } 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 { + InputStream fis = null; + try { + fis = new FileInputStream(kd); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate)cf.generateCertificate(fis); + cert.checkValidity(); + return cert.getPublicKey(); + } catch (GeneralSecurityException gse) { + IOException ioe = new IOException("cert error"); + ioe.initCause(gse); + throw ioe; + } finally { + try { if (fis != null) fis.close(); } catch (IOException foo) {} + } + } } diff --git a/core/java/src/net/i2p/crypto/SigType.java b/core/java/src/net/i2p/crypto/SigType.java index f63eadea84d21cf5fb6025e9b353b41953185c17..700f355c66747bb750c78993d79ac9de9e9de90a 100644 --- a/core/java/src/net/i2p/crypto/SigType.java +++ b/core/java/src/net/i2p/crypto/SigType.java @@ -178,6 +178,29 @@ public enum SigType { return true; } + /** + * @return true if supported in this JVM + * @since 0.9.15 + */ + public static boolean isAvailable(int code) { + SigType type = getByCode(code); + if (type == null) + return false; + return type.isAvailable(); + } + + /** + * @param stype number or name + * @return true if supported in this JVM + * @since 0.9.15 + */ + public static boolean isAvailable(String stype) { + SigType type = parseSigType(stype); + if (type == null) + return false; + return type.isAvailable(); + } + private static final Map<Integer, SigType> BY_CODE = new HashMap<Integer, SigType>(); static {