- enum for content type
  - fix NPE if private key not found
  - use certs instead of public keys for verification
  - improve validate-without-extract
  - new extract command
This commit is contained in:
zzz
2013-09-13 13:02:37 +00:00
parent 801ca47a0c
commit 4ffaf4128e
3 changed files with 139 additions and 67 deletions

View File

@@ -6,9 +6,13 @@ package net.i2p.crypto;
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import net.i2p.data.SigningPublicKey;
@@ -28,13 +32,25 @@ class DirKeyRing implements KeyRing {
public SigningPublicKey getKey(String keyName, String scope, SigType type)
throws GeneralSecurityException, IOException {
keyName = keyName.replace("@", "_at_");
File test = new File(keyName);
if (test.getParent() != null)
throw new IOException("bad key name");
File sd = new File(_base, scope);
File td = new File(sd, Integer.toString(type.getCode()));
File kd = new File(td, keyName + ".key");
//File td = new File(sd, Integer.toString(type.getCode()));
File kd = new File(sd, keyName + ".crt");
if (!kd.exists())
return null;
PublicKey pk = SigUtil.importJavaPublicKey(kd, type);
InputStream fis = null;
try {
fis = new FileInputStream(kd);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(fis);
PublicKey pk = cert.getPublicKey();
return SigUtil.fromJavaKey(pk, type);
} finally {
try { if (fis != null) fis.close(); } catch (IOException foo) {}
}
}
public void setKey(String keyName, String scope, SigningPublicKey key) {}

View File

@@ -330,7 +330,7 @@ public class KeyStoreUtil {
for (int i = 0; i < args.length; i++) {
buf.append('"').append(args[i]).append("\" ");
}
error("Failed to create SSL keystore using command line: " + buf, null);
error("Failed to generate keys using command line: " + buf, null);
}
return success;
}

View File

@@ -39,14 +39,13 @@ import net.i2p.util.SecureFileOutputStream;
public class SU3File {
private final I2PAppContext _context;
private final Map<SigningPublicKey, String> _trustedKeys;
private final File _file;
private String _version;
private int _versionLength;
private String _signer;
private int _signerLength;
private int _contentType;
private ContentType _contentType;
private long _contentLength;
private SigningPublicKey _signerPubkey;
private boolean _headerVerified;
@@ -64,37 +63,57 @@ public class SU3File {
private static final int CONTENT_PLUGIN = 2;
private static final int CONTENT_RESEED = 3;
private enum ContentType {
ROUTER(0, "update"),
PLUGIN(1, "plugin"),
RESEED(2, "reseed")
;
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; }
}
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);
}
}
/** @return null if not supported */
public static ContentType getByCode(int code) {
return BY_CODE.get(Integer.valueOf(code));
}
private static final SigType DEFAULT_TYPE = SigType.DSA_SHA1;
/**
* Uses TrustedUpdate's default keys for verification.
*
*/
public SU3File(String file) {
this(new File(file));
}
/**
* Uses TrustedUpdate's default keys for verification.
*
*/
public SU3File(File file) {
//this(file, (new TrustedUpdate()).getKeys());
this(file, null);
this(I2PAppContext.getGlobalContext(), file);
}
/**
* @param trustedKeys map of pubkey to signer name, null ok if not verifying
*
*/
public SU3File(File file, Map<SigningPublicKey, String> trustedKeys) {
this(I2PAppContext.getGlobalContext(), file, trustedKeys);
}
/**
* @param trustedKeys map of pubkey to signer name, null ok if not verifying
*/
public SU3File(I2PAppContext context, File file, Map<SigningPublicKey, String> trustedKeys) {
public SU3File(I2PAppContext context, File file) {
_context = context;
_file = file;
_trustedKeys = trustedKeys;
}
public String getVersionString() throws IOException {
@@ -138,11 +157,9 @@ public class SU3File {
int foo = in.read();
if (foo != FILE_VERSION)
throw new IOException("bad file version");
skip(in, 1);
int sigTypeCode = in.read();
int sigTypeCode = (int) DataHelper.readLong(in, 2);
_sigType = SigType.getByCode(sigTypeCode);
// TODO, for other known algos we must start over with a new MessageDigest
// (rewind 10 bytes)
// 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);
_signerLength = (int) DataHelper.readLong(in, 2);
@@ -164,9 +181,10 @@ public class SU3File {
if (foo != TYPE_ZIP)
throw new IOException("bad type");
skip(in, 1);
_contentType = in.read();
if (_contentType < CONTENT_ROUTER || _contentType > CONTENT_RESEED)
throw new IOException("bad content type");
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];
@@ -185,24 +203,16 @@ public class SU3File {
if (bytesRead != signerLen)
throw new EOFException();
_signer = DataHelper.getUTF8(data);
if (_trustedKeys != null) {
for (Map.Entry<SigningPublicKey, String> e : _trustedKeys.entrySet()) {
if (e.getValue().equals(_signer)) {
_signerPubkey = e.getKey();
break;
}
}
} else {
// testing
KeyRing ring = new DirKeyRing(new File("su3keyring"));
KeyRing ring = new DirKeyRing(new File(_context.getBaseDir(), "certificates"));
try {
_signerPubkey = ring.getKey(_signer, "default", _sigType);
_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);
_headerVerified = true;
@@ -221,13 +231,24 @@ public class SU3File {
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
* @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 {
@@ -264,6 +285,7 @@ public class SU3File {
skip(in, getContentOffset());
if (_signerPubkey == null)
throw new IOException("unknown signer: " + _signer);
if (migrateTo != null) // else verify only
out = new FileOutputStream(migrateTo);
byte[] buf = new byte[16*1024];
long tot = 0;
@@ -271,6 +293,7 @@ public class SU3File {
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;
}
@@ -290,7 +313,7 @@ public class SU3File {
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
if (out != null) try { out.close(); } catch (IOException ioe) {}
if (!rv)
if (migrateTo != null && !rv)
migrateTo.delete();
}
return rv;
@@ -319,8 +342,7 @@ public class SU3File {
out.write(MAGIC);
out.write((byte) 0);
out.write((byte) FILE_VERSION);
out.write((byte) 0);
out.write((byte) sigType.getCode());
DataHelper.writeLong(out, 2, sigType.getCode());
DataHelper.writeLong(out, 2, sigType.getSigLen());
out.write((byte) 0);
byte[] verBytes = DataHelper.getUTF8(version);
@@ -401,9 +423,11 @@ public class SU3File {
ok = verifySigCLI(args[1]);
} else if ("keygen".equals(args[0])) {
if (args[1].equals("-t"))
ok = genKeysCLI(args[2], args[3], args[4]);
ok = genKeysCLI(args[2], args[3], args[4], args[5]);
else
ok = genKeysCLI(args[1], args[2]);
ok = genKeysCLI(args[1], args[2], args[3]);
} else if ("extract".equals(args[0])) {
ok = extractCLI(args[1], args[2]);
} else {
showUsageCLI();
}
@@ -415,15 +439,16 @@ public class SU3File {
}
private static final void showUsageCLI() {
System.err.println("Usage: SU3File keygen [-t type|code] publicKeyFile privateKeyFile");
System.err.println("Usage: SU3File keygen [-t type|code] publicKeyFile keystore.ks you@mail.i2p");
System.err.println(" SU3File sign [-c type|code] [-t type|code] inputFile.zip signedFile.su3 keystore.ks version you@mail.i2p");
System.err.println(" SU3File showversion signedFile.su3");
System.err.println(" SU3File sign [-t type|code] inputFile.zip signedFile.su3 privateKeyFile version signerName@mail.i2p");
System.err.println(" SU3File verifysig signedFile.su3");
System.err.println(dumpSigTypes());
System.err.println(" SU3File extract signedFile.su3 outFile.zip");
System.err.println(dumpTypes());
}
/** @since 0.9.9 */
private static String dumpSigTypes() {
private static String dumpTypes() {
StringBuilder buf = new StringBuilder(256);
buf.append("Available signature types:\n");
for (SigType t : EnumSet.allOf(SigType.class)) {
@@ -432,6 +457,13 @@ public class SU3File {
buf.append(" DEFAULT");
buf.append('\n');
}
buf.append("Available content types:\n");
for (ContentType t : EnumSet.allOf(ContentType.class)) {
buf.append(" ").append(t).append("\t(code: ").append(t.getCode()).append(')');
if (t == ContentType.ROUTER)
buf.append(" DEFAULT");
buf.append('\n');
}
return buf.toString();
}
@@ -456,7 +488,7 @@ public class SU3File {
/** @return success */
private static final boolean showVersionCLI(String signedFile) {
try {
SU3File file = new SU3File(new File(signedFile), null);
SU3File file = new SU3File(signedFile);
String versionString = file.getVersionString();
if (versionString.equals(""))
System.out.println("No version string found in file '" + signedFile + "'");
@@ -512,6 +544,10 @@ public class SU3File {
}
File pkfile = new File(privateKeyFile);
PrivateKey pk = KeyStoreUtil.getPrivateKey(pkfile,KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD, signerName, keypw);
if (pk == null) {
System.out.println("Private key for " + signerName + " not found in keystore " + privateKeyFile);
return false;
}
SigningPrivateKey spk = SigUtil.fromJavaKey(pk, type);
SU3File file = new SU3File(signedFile);
file.write(new File(inputFile), CONTENT_ROUTER, version, signerName, spk);
@@ -533,8 +569,7 @@ public class SU3File {
InputStream in = null;
try {
SU3File file = new SU3File(signedFile);
//// fixme
boolean isValidSignature = file.verifyAndMigrate(new File("/dev/null"));
boolean isValidSignature = file.verify();
if (isValidSignature)
System.out.println("Signature VALID (signed by " + file.getSignerString() + ' ' + file._sigType + ')');
else
@@ -551,21 +586,43 @@ public class SU3File {
* @return success
* @since 0.9.9
*/
private static final boolean genKeysCLI(String publicKeyFile, String privateKeyFile) {
return genKeysCLI(DEFAULT_TYPE, publicKeyFile, privateKeyFile);
private static final boolean extractCLI(String signedFile, String outFile) {
InputStream in = null;
try {
SU3File file = new SU3File(signedFile);
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;
}
}
/**
* @return success
* @since 0.9.9
*/
private static final boolean genKeysCLI(String stype, String publicKeyFile, String privateKeyFile) {
private static final boolean genKeysCLI(String publicKeyFile, String privateKeyFile, String alias) {
return genKeysCLI(DEFAULT_TYPE, publicKeyFile, privateKeyFile, alias);
}
/**
* @return success
* @since 0.9.9
*/
private static final boolean genKeysCLI(String stype, String publicKeyFile, String privateKeyFile, String alias) {
SigType type = parseSigType(stype);
if (type == null) {
System.out.println("Signature type " + stype + " is not supported");
return false;
}
return genKeysCLI(type, publicKeyFile, privateKeyFile);
return genKeysCLI(type, publicKeyFile, privateKeyFile, alias);
}
/**
@@ -573,14 +630,13 @@ public class SU3File {
* @return success
* @since 0.9.9
*/
private static final boolean genKeysCLI(SigType type, String publicKeyFile, String privateKeyFile) {
private static final boolean genKeysCLI(SigType type, String publicKeyFile, String privateKeyFile, String alias) {
File pubFile = new File(publicKeyFile);
if (pubFile.exists()) {
System.out.println("Error: Not overwriting file " + publicKeyFile);
return false;
}
File ksFile = new File(privateKeyFile);
String alias = "";
String keypw = "";
try {
while (alias.length() == 0) {
@@ -603,7 +659,7 @@ public class SU3File {
keylen = 521;
}
boolean success = KeyStoreUtil.createKeys(ksFile, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD, alias,
"cn", "ou", 3652, type.getBaseAlgorithm().getName(),
alias, "I2P", 3652, type.getBaseAlgorithm().getName(),
keylen, keypw);
if (!success) {
System.err.println("Error writing keys:");