diff --git a/core/java/src/net/i2p/crypto/Hash384.java b/core/java/src/net/i2p/crypto/Hash384.java new file mode 100644 index 000000000..bdf60d00a --- /dev/null +++ b/core/java/src/net/i2p/crypto/Hash384.java @@ -0,0 +1,31 @@ +package net.i2p.crypto; + +/* + * free (adj.): unencumbered; not under the control of others + * No warranty of any kind, either expressed or implied. + */ + +import net.i2p.data.SimpleDataStructure; + +/** + * 48 byte hash + * + * @since 0.9.8 + */ +public class Hash384 extends SimpleDataStructure { + + public final static int HASH_LENGTH = 48; + + public Hash384() { + super(); + } + + /** @throws IllegalArgumentException if data is not correct length (null is ok) */ + public Hash384(byte data[]) { + super(data); + } + + public int length() { + return HASH_LENGTH; + } +} diff --git a/core/java/src/net/i2p/crypto/Hash512.java b/core/java/src/net/i2p/crypto/Hash512.java new file mode 100644 index 000000000..5916d2c53 --- /dev/null +++ b/core/java/src/net/i2p/crypto/Hash512.java @@ -0,0 +1,31 @@ +package net.i2p.crypto; + +/* + * free (adj.): unencumbered; not under the control of others + * No warranty of any kind, either expressed or implied. + */ + +import net.i2p.data.SimpleDataStructure; + +/** + * 64 byte hash + * + * @since 0.9.8 + */ +public class Hash512 extends SimpleDataStructure { + + public final static int HASH_LENGTH = 64; + + public Hash512() { + super(); + } + + /** @throws IllegalArgumentException if data is not correct length (null is ok) */ + public Hash512(byte data[]) { + super(data); + } + + public int length() { + return HASH_LENGTH; + } +} diff --git a/core/java/src/net/i2p/crypto/SU3File.java b/core/java/src/net/i2p/crypto/SU3File.java new file mode 100644 index 000000000..23d63f4ff --- /dev/null +++ b/core/java/src/net/i2p/crypto/SU3File.java @@ -0,0 +1,425 @@ +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.MessageDigest; +import java.util.HashMap; +import java.util.Map; + +import net.i2p.I2PAppContext; +import net.i2p.data.DataFormatException; +import net.i2p.data.DataHelper; +import net.i2p.data.Signature; +import net.i2p.data.SigningPrivateKey; +import net.i2p.data.SigningPublicKey; + +/** + * 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 Map _trustedKeys; + + private final File _file; + private String _version; + private int _versionLength; + private String _signer; + private int _signerLength; + private int _contentType; + private long _contentLength; + private SigningPublicKey _signerPubkey; + private boolean _headerVerified; + + private static final byte[] MAGIC = DataHelper.getUTF8("I2Psu3"); + private static final int FILE_VERSION = 0; + private static final int MIN_VERSION_BYTES = 16; + private static final int VERSION_OFFSET = Signature.SIGNATURE_BYTES; + + private static final int TYPE_ZIP = 0; + + private static final int CONTENT_ROUTER = 0; + private static final int CONTENT_ROUTER_P200 = 1; + private static final int CONTENT_PLUGIN = 2; + private static final int CONTENT_RESEED = 3; + + private static final int SIG_DSA_160 = SigType.DSA_SHA1.getCode(); + + /** + * 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()); + } + + /** + * @param trustedKeys map of pubkey to signer name, null ok if not verifying + */ + public SU3File(File file, Map 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 trustedKeys) { + _context = context; + _file = file; + _trustedKeys = trustedKeys; + } + + public String getVersionString() throws IOException { + verifyHeader(); + return _version; + } + + public String getSignerString() throws IOException { + verifyHeader(); + return _signer; + } + + /** + * 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.length]; + DataHelper.read(in, magic); + if (!DataHelper.eq(magic, MAGIC)) + 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 sigType = in.read(); + // TODO, for other known algos we must start over with a new MessageDigest + // (rewind 10 bytes) + if (sigType != SIG_DSA_160) + throw new IOException("bad sig type"); + _signerLength = (int) DataHelper.readLong(in, 2); + if (_signerLength != Signature.SIGNATURE_BYTES) + 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); + int signerLen = in.read(); + if (signerLen <= 0) + throw new IOException("bad signer length"); + _contentLength = DataHelper.readLong(in, 8); + if (_contentLength <= 0) + throw new IOException("bad content length"); + skip(in, 1); + foo = in.read(); + 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"); + 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[signerLen]; + bytesRead = DataHelper.read(in, data); + if (bytesRead != signerLen) + throw new EOFException(); + _signer = DataHelper.getUTF8(data); + if (_trustedKeys != null) { + for (Map.Entry e : _trustedKeys.entrySet()) { + if (e.getValue().equals(_signer)) { + _signerPubkey = e.getKey(); + break; + } + } + if (_signerPubkey == null) + throw new IOException("unknown signer: " + _signer); + } + _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 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 + * @return true if signature is good + */ + public boolean verifyAndMigrate(File migrateTo) throws IOException { + DigestInputStream in = null; + OutputStream out = null; + boolean rv = false; + try { + MessageDigest md = SHA1.getInstance(); + in = new DigestInputStream(new BufferedInputStream(new FileInputStream(_file)), md); + if (!_headerVerified) + verifyHeader(in); + else + skip(in, getContentOffset()); + if (_signerPubkey == null) + throw new IOException("unknown signer: " + _signer); + 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(); + out.write(buf, 0, read); + tot += read; + } + byte[] sha = md.digest(); + in.on(false); + Signature signature = new Signature(); + signature.readBytes(in); + SHA1Hash hash = new SHA1Hash(sha); + rv = _context.dsa().verifySignature(signature, hash, _signerPubkey); + } 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 (!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 contentType 0-255, 0 for zip + * @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 contentType, String version, + String signer, SigningPrivateKey privkey) throws IOException { + InputStream in = null; + DigestOutputStream out = null; + boolean ok = false; + try { + in = new BufferedInputStream(new FileInputStream(content)); + MessageDigest md = SHA1.getInstance(); + out = new DigestOutputStream(new BufferedOutputStream(new FileOutputStream(_file)), md); + out.write(MAGIC); + out.write((byte) 0); + out.write((byte) FILE_VERSION); + out.write((byte) 0); + out.write((byte) SIG_DSA_160); + DataHelper.writeLong(out, 2, Signature.SIGNATURE_BYTES); + 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); + out.write((byte) TYPE_ZIP); + 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); + SHA1Hash hash = new SHA1Hash(sha); + Signature signature = _context.dsa().sign(hash, privkey); + 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 { + if ("showversion".equals(args[0])) { + ok = showVersionCLI(args[1]); + } else if ("sign".equals(args[0])) { + ok = signCLI(args[1], args[2], args[3], args[4], args[5]); + } else if ("verifysig".equals(args[0])) { + ok = verifySigCLI(args[1]); + } else { + showUsageCLI(); + } + } catch (ArrayIndexOutOfBoundsException aioobe) { + showUsageCLI(); + } + if (!ok) + System.exit(1); + } + + private static final void showUsageCLI() { + System.err.println("Usage: SU3File showversion signedFile"); + System.err.println(" SU3File sign inputFile signedFile privateKeyFile version signerName"); + System.err.println(" SU3File verifysig signedFile"); + } + + /** @return success */ + private static final boolean showVersionCLI(String signedFile) { + try { + SU3File file = new SU3File(new File(signedFile), null); + String versionString = file.getVersionString(); + if (versionString.equals("")) + System.out.println("No version string found in file '" + signedFile + "'"); + else + System.out.println("Version: " + versionString); + return !versionString.equals(""); + } catch (IOException ioe) { + ioe.printStackTrace(); + return false; + } + } + + /** @return success */ + private static final boolean signCLI(String inputFile, String signedFile, String privateKeyFile, + String version, String signerName) { + InputStream in = null; + try { + in = new FileInputStream(privateKeyFile); + SigningPrivateKey spk = new SigningPrivateKey(); + spk.readBytes(in); + in.close(); + SU3File file = new SU3File(signedFile); + file.write(new File(inputFile), CONTENT_ROUTER, version, signerName, spk); + System.out.println("Input file '" + inputFile + "' signed and written to '" + signedFile + "'"); + return true; + } catch (DataFormatException dfe) { + System.out.println("Error signing input file '" + inputFile + "'"); + dfe.printStackTrace(); + return false; + } catch (IOException ioe) { + System.out.println("Error signing input file '" + inputFile + "'"); + ioe.printStackTrace(); + return false; + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + } + } + + /** @return valid */ + private static final boolean verifySigCLI(String signedFile) { + InputStream in = null; + try { + SU3File file = new SU3File(signedFile); + boolean isValidSignature = file.verifyAndMigrate(new File("/dev/null")); + if (isValidSignature) + System.out.println("Signature VALID (signed by " + file.getSignerString() + ')'); + else + System.out.println("Signature INVALID (signed by " + file.getSignerString() + ')'); + return isValidSignature; + } catch (IOException ioe) { + System.out.println("Error verifying input file '" + signedFile + "'"); + ioe.printStackTrace(); + return false; + } + } +} diff --git a/core/java/src/net/i2p/crypto/SigType.java b/core/java/src/net/i2p/crypto/SigType.java new file mode 100644 index 000000000..245711153 --- /dev/null +++ b/core/java/src/net/i2p/crypto/SigType.java @@ -0,0 +1,91 @@ +package net.i2p.crypto; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; + +/** + * Defines the properties for various signature types + * that I2P supports or may someday support. + * + * @since 0.9.8 + */ +public enum SigType { + /** + * DSA_SHA1 is the default. + * Pubkey 128 bytes; privkey 20 bytes; hash 20 bytes; sig 40 bytes + * @since 0.9.8 + */ + DSA_SHA1(0, 128, 20, 20, 40, "SHA-1", "SHA1withDSA"), + /** Pubkey 40 bytes; privkey 20 bytes; hash 20 bytes; sig 40 bytes */ + ECDSA_SHA1(1, 40, 20, 20, 40, "SHA-1", "SHA1withECDSA"), + /** Pubkey 64 bytes; privkey 32 bytes; hash 32 bytes; sig 64 bytes */ + ECDSA_SHA256(2, 64, 32, 32, 64, "SHA-256", "SHA256withECDSA"), + /** Pubkey 96 bytes; privkey 48 bytes; hash 48 bytes; sig 96 bytes */ + ECDSA_SHA384(3, 96, 48, 48, 96, "SHA-384", "SHA384withECDSA"), + /** Pubkey 128 bytes; privkey 64 bytes; hash 64 bytes; sig 128 bytes */ + ECDSA_SHA512(4, 128, 64, 64, 128, "SHA-512", "SHA512withECDSA") + + //MD5 + //ELGAMAL_SHA256 + //RSA_SHA1 + //RSA_SHA256 + //RSA_SHA384 + //RSA_SHA512 + //DSA_2048_224(2, 256, 28, 32, 56, "SHA-256"), + // Nonstandard, used by Syndie. + // Pubkey 128 bytes; privkey 20 bytes; hash 32 bytes; sig 40 bytes + //DSA_1024_160_SHA256(1, 128, 20, 32, 40, "SHA-256", "?"), + // Pubkey 256 bytes; privkey 32 bytes; hash 32 bytes; sig 64 bytes + //DSA_2048_256(2, 256, 32, 32, 64, "SHA-256", "?"), + // Pubkey 384 bytes; privkey 32 bytes; hash 32 bytes; sig 64 bytes + //DSA_3072_256(3, 384, 32, 32, 64, "SHA-256", "?"), + ; + + private final int code, pubkeyLen, privkeyLen, hashLen, sigLen; + private final String digestName, algoName; + + SigType(int cod, int pubLen, int privLen, int hLen, int sLen, String mdName, String aName) { + code = cod; + pubkeyLen = pubLen; + privkeyLen = privLen; + hashLen = hLen; + sigLen = sLen; + digestName = mdName; + algoName = aName; + } + + public int getCode() { return code; } + public int getPubkeyLen() { return pubkeyLen; } + public int getPrivkeyLen() { return privkeyLen; } + public int getHashLen() { return hashLen; } + public int getSigLen() { return sigLen; } + public String getAlgorithmName() { return algoName; } + + /** @throws UnsupportedOperationException if not supported */ + public MessageDigest getDigestInstance() { + if (digestName.equals("SHA-1")) + return SHA1.getInstance(); + if (digestName.equals("SHA-256")) + return SHA256Generator.getDigestInstance(); + try { + return MessageDigest.getInstance(digestName); + } catch (NoSuchAlgorithmException e) { + throw new UnsupportedOperationException(e); + } + } + + private static final Map BY_CODE = new HashMap(); + + static { + for (SigType type : SigType.values()) { + BY_CODE.put(Integer.valueOf(type.getCode()), type); + } + } + + /** @return null if not supported */ + public static SigType getByCode(int code) { + return BY_CODE.get(Integer.valueOf(code)); + } +} diff --git a/core/java/src/net/i2p/crypto/TrustedUpdate.java b/core/java/src/net/i2p/crypto/TrustedUpdate.java index 8ad2d14ed..51c19e4ea 100644 --- a/core/java/src/net/i2p/crypto/TrustedUpdate.java +++ b/core/java/src/net/i2p/crypto/TrustedUpdate.java @@ -8,6 +8,7 @@ import java.io.InputStream; import java.io.IOException; import java.io.SequenceInputStream; import java.io.UnsupportedEncodingException; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.StringTokenizer; @@ -209,6 +210,13 @@ riCe6OlAEiNpcc6mMyIYYWFICbrDFTrDR3wXqwc/Jkcx6L5VVWoagpSzbo3yGhc= _log.debug("TrustedUpdate created, trusting " + _trustedKeys.size() + " keys."); } + /** + * @since 0.9.8 + */ + Map getKeys() { + return Collections.unmodifiableMap(_trustedKeys); + } + /** * Duplicate keys or names rejected, * except that duplicate empty names are allowed diff --git a/core/java/src/net/i2p/data/Signature.java b/core/java/src/net/i2p/data/Signature.java index 0e6af4c04..412706302 100644 --- a/core/java/src/net/i2p/data/Signature.java +++ b/core/java/src/net/i2p/data/Signature.java @@ -9,6 +9,8 @@ package net.i2p.data; * */ +import net.i2p.crypto.SigType; + /** * Defines the signature as defined by the I2P data structure spec. * A signature is a 40-byte array verifying the authenticity of some data @@ -20,19 +22,47 @@ package net.i2p.data; * @author jrandom */ public class Signature extends SimpleDataStructure { - public final static int SIGNATURE_BYTES = 40; + private static final SigType DEF_TYPE = SigType.DSA_SHA1; + /** 40 */ + public final static int SIGNATURE_BYTES = DEF_TYPE.getSigLen(); /** all zeros */ public final static byte[] FAKE_SIGNATURE = new byte[SIGNATURE_BYTES]; + private final SigType _type; + public Signature() { + this(DEF_TYPE); + } + + /** + * @since 0.9.8 + */ + public Signature(SigType type) { super(); + _type = type; } public Signature(byte data[]) { - super(data); + this(DEF_TYPE, data); + } + + /** + * @since 0.9.8 + */ + public Signature(SigType type, byte data[]) { + super(); + _type = type; + setData(data); } public int length() { - return SIGNATURE_BYTES; + return _type.getSigLen(); + } + + /** + * @since 0.9.8 + */ + public SigType getType() { + return _type; } } diff --git a/core/java/src/net/i2p/data/SigningPrivateKey.java b/core/java/src/net/i2p/data/SigningPrivateKey.java index 4d804d080..818509b07 100644 --- a/core/java/src/net/i2p/data/SigningPrivateKey.java +++ b/core/java/src/net/i2p/data/SigningPrivateKey.java @@ -10,6 +10,7 @@ package net.i2p.data; */ import net.i2p.crypto.KeyGenerator; +import net.i2p.crypto.SigType; /** * Defines the SigningPrivateKey as defined by the I2P data structure spec. @@ -20,14 +21,34 @@ import net.i2p.crypto.KeyGenerator; * @author jrandom */ public class SigningPrivateKey extends SimpleDataStructure { - public final static int KEYSIZE_BYTES = 20; + private static final SigType DEF_TYPE = SigType.DSA_SHA1; + public final static int KEYSIZE_BYTES = DEF_TYPE.getPrivkeyLen(); + + private final SigType _type; public SigningPrivateKey() { + this(DEF_TYPE); + } + + /** + * @since 0.9.8 + */ + public SigningPrivateKey(SigType type) { super(); + _type = type; } public SigningPrivateKey(byte data[]) { - super(data); + this(DEF_TYPE, data); + } + + /** + * @since 0.9.8 + */ + public SigningPrivateKey(SigType type, byte data[]) { + super(); + _type = type; + setData(data); } /** constructs from base64 @@ -35,12 +56,20 @@ public class SigningPrivateKey extends SimpleDataStructure { * on a prior instance of SigningPrivateKey */ public SigningPrivateKey(String base64Data) throws DataFormatException { - super(); + this(); fromBase64(base64Data); } + public int length() { - return KEYSIZE_BYTES; + return _type.getPrivkeyLen(); + } + + /** + * @since 0.9.8 + */ + public SigType getType() { + return _type; } /** converts this signing private key to its public equivalent diff --git a/core/java/src/net/i2p/data/SigningPublicKey.java b/core/java/src/net/i2p/data/SigningPublicKey.java index 5839cf7e8..c1c9445d9 100644 --- a/core/java/src/net/i2p/data/SigningPublicKey.java +++ b/core/java/src/net/i2p/data/SigningPublicKey.java @@ -12,6 +12,8 @@ package net.i2p.data; import java.io.InputStream; import java.io.IOException; +import net.i2p.crypto.SigType; + /** * Defines the SigningPublicKey as defined by the I2P data structure spec. * A signing public key is 128 byte Integer. The public key represents only the @@ -21,11 +23,14 @@ import java.io.IOException; * @author jrandom */ public class SigningPublicKey extends SimpleDataStructure { - public final static int KEYSIZE_BYTES = 128; + private static final SigType DEF_TYPE = SigType.DSA_SHA1; + public final static int KEYSIZE_BYTES = DEF_TYPE.getPubkeyLen(); private static final int CACHE_SIZE = 1024; private static final SDSCache _cache = new SDSCache(SigningPublicKey.class, KEYSIZE_BYTES, CACHE_SIZE); + private final SigType _type; + /** * Pull from cache or return new * @throws AIOOBE if not enough bytes @@ -44,11 +49,28 @@ public class SigningPublicKey extends SimpleDataStructure { } public SigningPublicKey() { + this(DEF_TYPE); + } + + /** + * @since 0.9.8 + */ + public SigningPublicKey(SigType type) { super(); + _type = type; } public SigningPublicKey(byte data[]) { - super(data); + this(DEF_TYPE, data); + } + + /** + * @since 0.9.8 + */ + public SigningPublicKey(SigType type, byte data[]) { + super(); + _type = type; + setData(data); } /** constructs from base64 @@ -56,11 +78,18 @@ public class SigningPublicKey extends SimpleDataStructure { * on a prior instance of SigningPublicKey */ public SigningPublicKey(String base64Data) throws DataFormatException { - super(); + this(); fromBase64(base64Data); } public int length() { - return KEYSIZE_BYTES; + return _type.getPubkeyLen(); + } + + /** + * @since 0.9.8 + */ + public SigType getType() { + return _type; } } diff --git a/core/java/src/net/i2p/data/SimpleDataStructure.java b/core/java/src/net/i2p/data/SimpleDataStructure.java index 2084955b1..06fe7fb94 100644 --- a/core/java/src/net/i2p/data/SimpleDataStructure.java +++ b/core/java/src/net/i2p/data/SimpleDataStructure.java @@ -31,17 +31,13 @@ import net.i2p.crypto.SHA256Generator; */ public abstract class SimpleDataStructure extends DataStructureImpl { protected byte[] _data; - /** this is just to avoid lots of calls to length() */ - protected final int _length; /** A new instance with the data set to null. Call readBytes(), setData(), or fromByteArray() after this to set the data */ public SimpleDataStructure() { - _length = length(); } /** @throws IllegalArgumentException if data is not the legal number of bytes (but null is ok) */ public SimpleDataStructure(byte data[]) { - _length = length(); setData(data); } @@ -68,8 +64,8 @@ public abstract class SimpleDataStructure extends DataStructureImpl { public void setData(byte[] data) { if (_data != null) throw new RuntimeException("Data already set"); - if (data != null && data.length != _length) - throw new IllegalArgumentException("Bad data length: " + data.length + "; required: " + _length); + if (data != null && data.length != length()) + throw new IllegalArgumentException("Bad data length: " + data.length + "; required: " + length()); _data = data; } @@ -81,9 +77,10 @@ public abstract class SimpleDataStructure extends DataStructureImpl { public void readBytes(InputStream in) throws DataFormatException, IOException { if (_data != null) throw new RuntimeException("Data already set"); - _data = new byte[_length]; + int length = length(); + _data = new byte[length]; int read = read(in, _data); - if (read != _length) throw new DataFormatException("Not enough bytes to read the data"); + if (read != length) throw new DataFormatException("Not enough bytes to read the data"); } public void writeBytes(OutputStream out) throws DataFormatException, IOException { @@ -109,8 +106,8 @@ public abstract class SimpleDataStructure extends DataStructureImpl { byte[] d = Base64.decode(data); if (d == null) throw new DataFormatException("Bad Base64 encoded data"); - if (d.length != _length) - throw new DataFormatException("Bad decoded data length, expected " + _length + " got " + d.length); + if (d.length != length()) + throw new DataFormatException("Bad decoded data length, expected " + length() + " got " + d.length); // call setData() instead of _data = data in case overridden setData(d); } @@ -141,8 +138,8 @@ public abstract class SimpleDataStructure extends DataStructureImpl { @Override public void fromByteArray(byte data[]) throws DataFormatException { if (data == null) throw new DataFormatException("Null data passed in"); - if (data.length != _length) - throw new DataFormatException("Bad data length: " + data.length + "; required: " + _length); + if (data.length != length()) + throw new DataFormatException("Bad data length: " + data.length + "; required: " + length()); // call setData() instead of _data = data in case overridden setData(data); } @@ -151,12 +148,13 @@ public abstract class SimpleDataStructure extends DataStructureImpl { public String toString() { StringBuilder buf = new StringBuilder(64); buf.append('[').append(getClass().getSimpleName()).append(": "); + int length = length(); if (_data == null) { buf.append("null"); - } else if (_length <= 32) { + } else if (length <= 32) { buf.append(toBase64()); } else { - buf.append("size: ").append(Integer.toString(_length)); + buf.append("size: ").append(Integer.toString(length)); } buf.append(']'); return buf.toString(); diff --git a/history.txt b/history.txt index a219a8e61..3ddf2863b 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,11 @@ +2012-07-29 zzz + * Signatures: + - Prep for new signature algorithms; new SigType enum; + Signature, SigningPublicKey, SigningPrivateKey store type + - New Hash384 and Hash512 classes + - Remove length field in SimpleDataStructure + - New SU3File generator/verifier/extractor + 2012-07-28 zzz * Addresses: Treat RFC 4193 addresses fc00::/7 as local * NetDB: Disable RI verifies for now diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 7f4edb975..31c35f8c3 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 13; + public final static long BUILD = 14; /** for example "-test" */ public final static String EXTRA = "";