forked from I2P_Developers/i2p.i2p
* Certificates:
- Add a signed Certificate type - Add a main() to PrivateKeyFile to generate Destinations with various Certificate types - Add a VerifiedDestination class to check Certificates of various types - Add a HashCash library from http://www.nettgryppa.com/code/ (no distribution restrictions) - Allow non-null Certificates in addressbook
This commit is contained in:
@ -159,6 +159,9 @@ public class AddressBook {
|
|||||||
return this.addresses.toString();
|
return this.addresses.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final int MIN_DEST_LENGTH = 516;
|
||||||
|
private static final int MAX_DEST_LENGTH = MIN_DEST_LENGTH + 100; // longer than any known cert type for now
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do basic validation of the hostname and dest
|
* Do basic validation of the hostname and dest
|
||||||
* hostname was already converted to lower case by ConfigParser.parse()
|
* hostname was already converted to lower case by ConfigParser.parse()
|
||||||
@ -184,8 +187,8 @@ public class AddressBook {
|
|||||||
(! host.endsWith(".router.i2p")) &&
|
(! host.endsWith(".router.i2p")) &&
|
||||||
(! host.endsWith(".console.i2p")) &&
|
(! host.endsWith(".console.i2p")) &&
|
||||||
|
|
||||||
dest.length() == 516 &&
|
((dest.length() == MIN_DEST_LENGTH && dest.endsWith("AAAA")) ||
|
||||||
dest.endsWith("AAAA") &&
|
(dest.length() > MIN_DEST_LENGTH && dest.length() <= MAX_DEST_LENGTH)) &&
|
||||||
dest.replaceAll("[a-zA-Z0-9~-]", "").length() == 0
|
dest.replaceAll("[a-zA-Z0-9~-]", "").length() == 0
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ div.routersummary {
|
|||||||
color: inherit;
|
color: inherit;
|
||||||
font-size: small;
|
font-size: small;
|
||||||
clear: left; /* fixes a bug in Opera */
|
clear: left; /* fixes a bug in Opera */
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.warning {
|
div.warning {
|
||||||
|
480
core/java/src/com/nettgryppa/security/HashCash.java
Normal file
480
core/java/src/com/nettgryppa/security/HashCash.java
Normal file
@ -0,0 +1,480 @@
|
|||||||
|
package com.nettgryppa.security;
|
||||||
|
// Copyright 2006 Gregory Rubin grrubin@gmail.com
|
||||||
|
// Permission is given to use, modify, and or distribute this code so long as this message remains attached
|
||||||
|
// Please see the spec at: http://www.hashcash.org/
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for generation and parsing of <a href="http://www.hashcash.org/">HashCash</a><br>
|
||||||
|
* Copyright 2006 Gregory Rubin <a href="mailto:grrubin@gmail.com">grrubin@gmail.com</a><br>
|
||||||
|
* Permission is given to use, modify, and or distribute this code so long as this message remains attached<br>
|
||||||
|
* Please see the spec at: <a href="http://www.hashcash.org/">http://www.hashcash.org/</a>
|
||||||
|
* @author grrubin@gmail.com
|
||||||
|
* @version 1.1
|
||||||
|
*/
|
||||||
|
public class HashCash implements Comparable<HashCash> {
|
||||||
|
public static final int DefaultVersion = 1;
|
||||||
|
private static final int hashLength = 160;
|
||||||
|
private static final String dateFormatString = "yyMMdd";
|
||||||
|
private static long milliFor16 = -1;
|
||||||
|
|
||||||
|
private String myToken;
|
||||||
|
private int myValue;
|
||||||
|
private Calendar myDate;
|
||||||
|
private Map<String, List<String> > myExtensions;
|
||||||
|
private int myVersion;
|
||||||
|
private String myResource;
|
||||||
|
// Constructors
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses and validates a HashCash.
|
||||||
|
* @throws NoSuchAlgorithmException If SHA1 is not a supported Message Digest
|
||||||
|
*/
|
||||||
|
public HashCash(String cash) throws NoSuchAlgorithmException {
|
||||||
|
myToken = cash;
|
||||||
|
String[] parts = cash.split(":");
|
||||||
|
myVersion = Integer.parseInt(parts[0]);
|
||||||
|
if(myVersion < 0 || myVersion > 1)
|
||||||
|
throw new IllegalArgumentException("Only supported versions are 0 and 1");
|
||||||
|
|
||||||
|
if((myVersion == 0 && parts.length != 6) ||
|
||||||
|
(myVersion == 1 && parts.length != 7))
|
||||||
|
throw new IllegalArgumentException("Improperly formed HashCash");
|
||||||
|
|
||||||
|
try {
|
||||||
|
int index = 1;
|
||||||
|
if(myVersion == 1)
|
||||||
|
myValue = Integer.parseInt(parts[index++]);
|
||||||
|
else
|
||||||
|
myValue = 0;
|
||||||
|
|
||||||
|
SimpleDateFormat dateFormat = new SimpleDateFormat(dateFormatString);
|
||||||
|
Calendar tempCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
|
||||||
|
tempCal.setTime(dateFormat.parse(parts[index++]));
|
||||||
|
|
||||||
|
myResource = parts[index++];
|
||||||
|
myExtensions = deserializeExtensions(parts[index++]);
|
||||||
|
|
||||||
|
MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||||
|
md.update(cash.getBytes());
|
||||||
|
byte[] tempBytes = md.digest();
|
||||||
|
int tempValue = numberOfLeadingZeros(tempBytes);
|
||||||
|
|
||||||
|
if(myVersion == 0)
|
||||||
|
myValue = tempValue;
|
||||||
|
else if (myVersion == 1)
|
||||||
|
myValue = (tempValue > myValue ? myValue : tempValue);
|
||||||
|
} catch (java.text.ParseException ex) {
|
||||||
|
throw new IllegalArgumentException("Improperly formed HashCash", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashCash() throws NoSuchAlgorithmException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mints a version 1 HashCash using now as the date
|
||||||
|
* @param resource the string to be encoded in the HashCash
|
||||||
|
* @throws NoSuchAlgorithmException If SHA1 is not a supported Message Digest
|
||||||
|
*/
|
||||||
|
public static HashCash mintCash(String resource, int value) throws NoSuchAlgorithmException {
|
||||||
|
Calendar now = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
|
||||||
|
return mintCash(resource, null, now, value, DefaultVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mints a HashCash using now as the date
|
||||||
|
* @param resource the string to be encoded in the HashCash
|
||||||
|
* @param version Which version to mint. Only valid values are 0 and 1
|
||||||
|
* @throws NoSuchAlgorithmException If SHA1 is not a supported Message Digest
|
||||||
|
*/
|
||||||
|
public static HashCash mintCash(String resource, int value, int version) throws NoSuchAlgorithmException {
|
||||||
|
Calendar now = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
|
||||||
|
return mintCash(resource, null, now, value, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mints a version 1 HashCash
|
||||||
|
* @param resource the string to be encoded in the HashCash
|
||||||
|
* @throws NoSuchAlgorithmException If SHA1 is not a supported Message Digest
|
||||||
|
*/
|
||||||
|
public static HashCash mintCash(String resource, Calendar date, int value) throws NoSuchAlgorithmException {
|
||||||
|
return mintCash(resource, null, date, value, DefaultVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mints a HashCash
|
||||||
|
* @param resource the string to be encoded in the HashCash
|
||||||
|
* @param version Which version to mint. Only valid values are 0 and 1
|
||||||
|
* @throws NoSuchAlgorithmException If SHA1 is not a supported Message Digest
|
||||||
|
*/
|
||||||
|
public static HashCash mintCash(String resource, Calendar date, int value, int version)
|
||||||
|
throws NoSuchAlgorithmException {
|
||||||
|
return mintCash(resource, null, date, value, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mints a version 1 HashCash using now as the date
|
||||||
|
* @param resource the string to be encoded in the HashCash
|
||||||
|
* @param extensions Extra data to be encoded in the HashCash
|
||||||
|
* @throws NoSuchAlgorithmException If SHA1 is not a supported Message Digest
|
||||||
|
*/
|
||||||
|
public static HashCash mintCash(String resource, Map<String, List<String> > extensions, int value)
|
||||||
|
throws NoSuchAlgorithmException {
|
||||||
|
Calendar now = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
|
||||||
|
return mintCash(resource, extensions, now, value, DefaultVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mints a HashCash using now as the date
|
||||||
|
* @param resource the string to be encoded in the HashCash
|
||||||
|
* @param extensions Extra data to be encoded in the HashCash
|
||||||
|
* @param version Which version to mint. Only valid values are 0 and 1
|
||||||
|
* @throws NoSuchAlgorithmException If SHA1 is not a supported Message Digest
|
||||||
|
*/
|
||||||
|
public static HashCash mintCash(String resource, Map<String, List<String> > extensions, int value, int version)
|
||||||
|
throws NoSuchAlgorithmException {
|
||||||
|
Calendar now = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
|
||||||
|
return mintCash(resource, extensions, now, value, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mints a version 1 HashCash
|
||||||
|
* @param resource the string to be encoded in the HashCash
|
||||||
|
* @param extensions Extra data to be encoded in the HashCash
|
||||||
|
* @throws NoSuchAlgorithmException If SHA1 is not a supported Message Digest
|
||||||
|
*/
|
||||||
|
public static HashCash mintCash(String resource, Map<String, List<String> > extensions, Calendar date, int value)
|
||||||
|
throws NoSuchAlgorithmException {
|
||||||
|
return mintCash(resource, extensions, date, value, DefaultVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mints a HashCash
|
||||||
|
* @param resource the string to be encoded in the HashCash
|
||||||
|
* @param extensions Extra data to be encoded in the HashCash
|
||||||
|
* @param version Which version to mint. Only valid values are 0 and 1
|
||||||
|
* @throws NoSuchAlgorithmException If SHA1 is not a supported Message Digest
|
||||||
|
*/
|
||||||
|
public static HashCash mintCash(String resource, Map<String, List<String> > extensions, Calendar date, int value, int version)
|
||||||
|
throws NoSuchAlgorithmException {
|
||||||
|
if(version < 0 || version > 1)
|
||||||
|
throw new IllegalArgumentException("Only supported versions are 0 and 1");
|
||||||
|
|
||||||
|
if(value < 0 || value > hashLength)
|
||||||
|
throw new IllegalArgumentException("Value must be between 0 and " + hashLength);
|
||||||
|
|
||||||
|
if(resource.contains(":"))
|
||||||
|
throw new IllegalArgumentException("Resource may not contain a colon.");
|
||||||
|
|
||||||
|
HashCash result = new HashCash();
|
||||||
|
|
||||||
|
MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||||
|
|
||||||
|
result.myResource = resource;
|
||||||
|
result.myExtensions = (null == extensions ? new HashMap<String, List<String> >() : extensions);
|
||||||
|
result.myDate = date;
|
||||||
|
result.myVersion = version;
|
||||||
|
|
||||||
|
String prefix;
|
||||||
|
|
||||||
|
SimpleDateFormat dateFormat = new SimpleDateFormat(dateFormatString);
|
||||||
|
switch(version) {
|
||||||
|
case 0:
|
||||||
|
prefix = version + ":" + dateFormat.format(date.getTime()) + ":" + resource + ":" +
|
||||||
|
serializeExtensions(extensions) + ":";
|
||||||
|
result.myToken = generateCash(prefix, value, md);
|
||||||
|
md.reset();
|
||||||
|
md.update(result.myToken.getBytes());
|
||||||
|
result.myValue = numberOfLeadingZeros(md.digest());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
result.myValue = value;
|
||||||
|
prefix = version + ":" + value + ":" + dateFormat.format(date.getTime()) + ":" + resource + ":" +
|
||||||
|
serializeExtensions(extensions) + ":";
|
||||||
|
result.myToken = generateCash(prefix, value, md);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Only supported versions are 0 and 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accessors
|
||||||
|
/**
|
||||||
|
* Two objects are considered equal if they are both of type HashCash and have an identical string representation
|
||||||
|
*/
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if(obj instanceof HashCash)
|
||||||
|
return toString().equals(obj.toString());
|
||||||
|
else
|
||||||
|
return super.equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the canonical string representation of the HashCash
|
||||||
|
*/
|
||||||
|
public String toString() {
|
||||||
|
return myToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extra data encoded in the HashCash
|
||||||
|
*/
|
||||||
|
public Map<String, List<String> > getExtensions() {
|
||||||
|
return myExtensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The primary resource being protected
|
||||||
|
*/
|
||||||
|
public String getResource() {
|
||||||
|
return myResource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minting date
|
||||||
|
*/
|
||||||
|
public Calendar getDate() {
|
||||||
|
return myDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value of the HashCash (e.g. how many leading zero bits it has)
|
||||||
|
*/
|
||||||
|
public int getValue() {
|
||||||
|
return myValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which version of HashCash is used here
|
||||||
|
*/
|
||||||
|
public int getVersion() {
|
||||||
|
return myVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private utility functions
|
||||||
|
/**
|
||||||
|
* Actually tries various combinations to find a valid hash. Form is of prefix + random_hex + ":" + random_hex
|
||||||
|
* @throws NoSuchAlgorithmException If SHA1 is not a supported Message Digest
|
||||||
|
*/
|
||||||
|
private static String generateCash(String prefix, int value, MessageDigest md)
|
||||||
|
throws NoSuchAlgorithmException {
|
||||||
|
SecureRandom rnd = SecureRandom.getInstance("SHA1PRNG");
|
||||||
|
byte[] tmpBytes = new byte[8];
|
||||||
|
rnd.nextBytes(tmpBytes);
|
||||||
|
long random = bytesToLong(tmpBytes);
|
||||||
|
rnd.nextBytes(tmpBytes);
|
||||||
|
long counter = bytesToLong(tmpBytes);
|
||||||
|
|
||||||
|
prefix = prefix + Long.toHexString(random) + ":";
|
||||||
|
|
||||||
|
String temp;
|
||||||
|
int tempValue;
|
||||||
|
byte[] bArray;
|
||||||
|
do {
|
||||||
|
counter++;
|
||||||
|
temp = prefix + Long.toHexString(counter);
|
||||||
|
md.reset();
|
||||||
|
md.update(temp.getBytes());
|
||||||
|
bArray = md.digest();
|
||||||
|
tempValue = numberOfLeadingZeros(bArray);
|
||||||
|
} while ( tempValue < value);
|
||||||
|
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a 8 byte array of unsigned bytes to an long
|
||||||
|
* @param b an array of 8 unsigned bytes
|
||||||
|
*/
|
||||||
|
private static long bytesToLong(byte[] b) {
|
||||||
|
long l = 0;
|
||||||
|
l |= b[0] & 0xFF;
|
||||||
|
l <<= 8;
|
||||||
|
l |= b[1] & 0xFF;
|
||||||
|
l <<= 8;
|
||||||
|
l |= b[2] & 0xFF;
|
||||||
|
l <<= 8;
|
||||||
|
l |= b[3] & 0xFF;
|
||||||
|
l <<= 8;
|
||||||
|
l |= b[4] & 0xFF;
|
||||||
|
l <<= 8;
|
||||||
|
l |= b[5] & 0xFF;
|
||||||
|
l <<= 8;
|
||||||
|
l |= b[6] & 0xFF;
|
||||||
|
l <<= 8;
|
||||||
|
l |= b[7] & 0xFF;
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes the extensions with (key, value) seperated by semi-colons and values seperated by commas
|
||||||
|
*/
|
||||||
|
private static String serializeExtensions(Map<String, List<String> > extensions) {
|
||||||
|
if(null == extensions || extensions.isEmpty())
|
||||||
|
return "";
|
||||||
|
|
||||||
|
StringBuffer result = new StringBuffer();
|
||||||
|
List<String> tempList;
|
||||||
|
boolean first = true;
|
||||||
|
|
||||||
|
for(String key: extensions.keySet()) {
|
||||||
|
if(key.contains(":") || key.contains(";") || key.contains("="))
|
||||||
|
throw new IllegalArgumentException("Extension key contains an illegal character. " + key);
|
||||||
|
if(!first)
|
||||||
|
result.append(";");
|
||||||
|
first = false;
|
||||||
|
result.append(key);
|
||||||
|
tempList = extensions.get(key);
|
||||||
|
|
||||||
|
if(null != tempList) {
|
||||||
|
result.append("=");
|
||||||
|
for(int i = 0; i < tempList.size(); i++) {
|
||||||
|
if(tempList.get(i).contains(":") || tempList.get(i).contains(";") || tempList.get(i).contains(","))
|
||||||
|
throw new IllegalArgumentException("Extension value contains an illegal character. " + tempList.get(i));
|
||||||
|
if(i > 0)
|
||||||
|
result.append(",");
|
||||||
|
result.append(tempList.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inverse of {@link #serializeExtensions(Map)}
|
||||||
|
*/
|
||||||
|
private static Map<String, List<String> > deserializeExtensions(String extensions) {
|
||||||
|
Map<String, List<String> > result = new HashMap<String, List<String> >();
|
||||||
|
if(null == extensions || extensions.length() == 0)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
String[] items = extensions.split(";");
|
||||||
|
|
||||||
|
for(int i = 0; i < items.length; i++) {
|
||||||
|
String[] parts = items[i].split("=", 2);
|
||||||
|
if(parts.length == 1)
|
||||||
|
result.put(parts[0], null);
|
||||||
|
else
|
||||||
|
result.put(parts[0], Arrays.asList(parts[1].split(",")));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the number of leading zeros in a byte array.
|
||||||
|
*/
|
||||||
|
private static int numberOfLeadingZeros(byte[] values) {
|
||||||
|
int result = 0;
|
||||||
|
int temp = 0;
|
||||||
|
for(int i = 0; i < values.length; i++) {
|
||||||
|
|
||||||
|
temp = numberOfLeadingZeros(values[i]);
|
||||||
|
|
||||||
|
result += temp;
|
||||||
|
if(temp != 8)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of leading zeros in a bytes binary represenation
|
||||||
|
*/
|
||||||
|
private static int numberOfLeadingZeros(byte value) {
|
||||||
|
if(value < 0)
|
||||||
|
return 0;
|
||||||
|
if(value < 1)
|
||||||
|
return 8;
|
||||||
|
else if (value < 2)
|
||||||
|
return 7;
|
||||||
|
else if (value < 4)
|
||||||
|
return 6;
|
||||||
|
else if (value < 8)
|
||||||
|
return 5;
|
||||||
|
else if (value < 16)
|
||||||
|
return 4;
|
||||||
|
else if (value < 32)
|
||||||
|
return 3;
|
||||||
|
else if (value < 64)
|
||||||
|
return 2;
|
||||||
|
else if (value < 128)
|
||||||
|
return 1;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimates how many milliseconds it would take to mint a cash of the specified value.
|
||||||
|
* <ul>
|
||||||
|
* <li>NOTE1: Minting time can vary greatly in fact, half of the time it will take half as long)
|
||||||
|
* <li>NOTE2: The first time that an estimation function is called it is expensive (on the order of seconds). After that, it is very quick.
|
||||||
|
* </ul>
|
||||||
|
* @throws NoSuchAlgorithmException If SHA1 is not a supported Message Digest
|
||||||
|
*/
|
||||||
|
public static long estimateTime(int value) throws NoSuchAlgorithmException {
|
||||||
|
initEstimates();
|
||||||
|
return (long)(milliFor16 * Math.pow(2, value - 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimates what value (e.g. how many bits of collision) are required for the specified length of time.
|
||||||
|
* <ul>
|
||||||
|
* <li>NOTE1: Minting time can vary greatly in fact, half of the time it will take half as long)
|
||||||
|
* <li>NOTE2: The first time that an estimation function is called it is expensive (on the order of seconds). After that, it is very quick.
|
||||||
|
* </ul>
|
||||||
|
* @throws NoSuchAlgorithmException If SHA1 is not a supported Message Digest
|
||||||
|
*/
|
||||||
|
public static int estimateValue(int secs) throws NoSuchAlgorithmException {
|
||||||
|
initEstimates();
|
||||||
|
int result = 0;
|
||||||
|
long millis = secs * 1000 * 65536;
|
||||||
|
millis /= milliFor16;
|
||||||
|
|
||||||
|
while(millis > 1) {
|
||||||
|
result++;
|
||||||
|
millis /= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seeds the estimates by determining how long it takes to calculate a 16bit collision on average.
|
||||||
|
* @throws NoSuchAlgorithmException If SHA1 is not a supported Message Digest
|
||||||
|
*/
|
||||||
|
private static void initEstimates() throws NoSuchAlgorithmException {
|
||||||
|
if(milliFor16 == -1) {
|
||||||
|
long duration;
|
||||||
|
duration = Calendar.getInstance().getTimeInMillis();
|
||||||
|
for(int i = 0; i < 11; i++) {
|
||||||
|
mintCash("estimation", 16);
|
||||||
|
}
|
||||||
|
duration = Calendar.getInstance().getTimeInMillis() - duration;
|
||||||
|
milliFor16 = (duration /10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares the value of two HashCashes
|
||||||
|
* @param other
|
||||||
|
* @see java.lang.Comparable#compareTo(Object)
|
||||||
|
*/
|
||||||
|
public int compareTo(HashCash other) {
|
||||||
|
if(null == other)
|
||||||
|
throw new NullPointerException();
|
||||||
|
|
||||||
|
return Integer.valueOf(getValue()).compareTo(Integer.valueOf(other.getValue()));
|
||||||
|
}
|
||||||
|
}
|
@ -36,6 +36,9 @@ public class Certificate extends DataStructureImpl {
|
|||||||
public final static int CERTIFICATE_TYPE_HASHCASH = 1;
|
public final static int CERTIFICATE_TYPE_HASHCASH = 1;
|
||||||
/** we should not be used for anything (don't use us in the netDb, in tunnels, or tell others about us) */
|
/** we should not be used for anything (don't use us in the netDb, in tunnels, or tell others about us) */
|
||||||
public final static int CERTIFICATE_TYPE_HIDDEN = 2;
|
public final static int CERTIFICATE_TYPE_HIDDEN = 2;
|
||||||
|
/** Signed with 40-byte Signature and (optional) 32-byte hash */
|
||||||
|
public final static int CERTIFICATE_TYPE_SIGNED = 3;
|
||||||
|
public final static int CERTIFICATE_LENGTH_SIGNED_WITH_HASH = Signature.SIGNATURE_BYTES + Hash.HASH_LENGTH;
|
||||||
|
|
||||||
public Certificate() {
|
public Certificate() {
|
||||||
_type = 0;
|
_type = 0;
|
||||||
@ -149,18 +152,28 @@ public class Certificate extends DataStructureImpl {
|
|||||||
buf.append("Null certificate");
|
buf.append("Null certificate");
|
||||||
else if (getCertificateType() == CERTIFICATE_TYPE_HASHCASH)
|
else if (getCertificateType() == CERTIFICATE_TYPE_HASHCASH)
|
||||||
buf.append("Hashcash certificate");
|
buf.append("Hashcash certificate");
|
||||||
|
else if (getCertificateType() == CERTIFICATE_TYPE_HIDDEN)
|
||||||
|
buf.append("Hidden certificate");
|
||||||
|
else if (getCertificateType() == CERTIFICATE_TYPE_SIGNED)
|
||||||
|
buf.append("Signed certificate");
|
||||||
else
|
else
|
||||||
buf.append("Unknown certificiate type (").append(getCertificateType()).append(")");
|
buf.append("Unknown certificate type (").append(getCertificateType()).append(")");
|
||||||
|
|
||||||
if (_payload == null) {
|
if (_payload == null) {
|
||||||
buf.append(" null payload");
|
buf.append(" null payload");
|
||||||
} else {
|
} else {
|
||||||
buf.append(" payload size: ").append(_payload.length);
|
buf.append(" payload size: ").append(_payload.length);
|
||||||
|
if (getCertificateType() == CERTIFICATE_TYPE_HASHCASH) {
|
||||||
|
buf.append(" Stamp: ").append(new String(_payload));
|
||||||
|
} else if (getCertificateType() == CERTIFICATE_TYPE_SIGNED && _payload.length == CERTIFICATE_LENGTH_SIGNED_WITH_HASH) {
|
||||||
|
buf.append(" Signed by hash: ").append(Base64.encode(_payload, Signature.SIGNATURE_BYTES, Hash.HASH_LENGTH));
|
||||||
|
} else {
|
||||||
int len = 32;
|
int len = 32;
|
||||||
if (len > _payload.length) len = _payload.length;
|
if (len > _payload.length) len = _payload.length;
|
||||||
buf.append(" first ").append(len).append(" bytes: ");
|
buf.append(" first ").append(len).append(" bytes: ");
|
||||||
buf.append(DataHelper.toString(_payload, len));
|
buf.append(DataHelper.toString(_payload, len));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
buf.append("]");
|
buf.append("]");
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
@ -17,17 +17,17 @@ import java.io.OutputStream;
|
|||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines an end point in the I2P network. The Destination may move aroundn
|
* Defines an end point in the I2P network. The Destination may move around
|
||||||
* in the network, but messages sent to the Destination will find it
|
* in the network, but messages sent to the Destination will find it
|
||||||
*
|
*
|
||||||
* @author jrandom
|
* @author jrandom
|
||||||
*/
|
*/
|
||||||
public class Destination extends DataStructureImpl {
|
public class Destination extends DataStructureImpl {
|
||||||
private final static Log _log = new Log(Destination.class);
|
protected final static Log _log = new Log(Destination.class);
|
||||||
private Certificate _certificate;
|
protected Certificate _certificate;
|
||||||
private SigningPublicKey _signingKey;
|
protected SigningPublicKey _signingKey;
|
||||||
private PublicKey _publicKey;
|
protected PublicKey _publicKey;
|
||||||
private Hash __calculatedHash;
|
protected Hash __calculatedHash;
|
||||||
|
|
||||||
public Destination() {
|
public Destination() {
|
||||||
setCertificate(null);
|
setCertificate(null);
|
||||||
|
@ -1,38 +1,78 @@
|
|||||||
package net.i2p.data;
|
package net.i2p.data;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import com.nettgryppa.security.HashCash;
|
||||||
|
|
||||||
import net.i2p.I2PException;
|
import net.i2p.I2PException;
|
||||||
import net.i2p.client.I2PClient;
|
import net.i2p.client.I2PClient;
|
||||||
|
import net.i2p.client.I2PClientFactory;
|
||||||
import net.i2p.client.I2PSession;
|
import net.i2p.client.I2PSession;
|
||||||
import net.i2p.client.I2PSessionException;
|
import net.i2p.client.I2PSessionException;
|
||||||
|
import net.i2p.crypto.DSAEngine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This helper class reads and writes files in the
|
||||||
|
* same "eepPriv.dat" format used by the client code.
|
||||||
|
* The format is:
|
||||||
|
* - Destination (387 bytes if no certificate, otherwise longer)
|
||||||
|
* - Public key (256 bytes)
|
||||||
|
* - Signing Public key (128 bytes)
|
||||||
|
* - Cert. type (1 byte)
|
||||||
|
* - Cert. length (2 bytes)
|
||||||
|
* - Certificate if length != 0
|
||||||
|
* - Private key (256 bytes)
|
||||||
|
* - Signing Private key (20 bytes)
|
||||||
|
* Total 663 bytes
|
||||||
|
*
|
||||||
|
* @author welterde, zzz
|
||||||
|
*/
|
||||||
|
|
||||||
public class PrivateKeyFile {
|
public class PrivateKeyFile {
|
||||||
public PrivateKeyFile(File file, I2PClient client) {
|
public PrivateKeyFile(File file, I2PClient client) {
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.dest = null;
|
this.dest = null;
|
||||||
|
this.privKey = null;
|
||||||
|
this.signingPrivKey = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void createIfAbsent() throws I2PException, IOException {
|
/** Also reads in the file to get the privKay and signingPrivKey,
|
||||||
|
* which aren't available from I2PClient.
|
||||||
|
*/
|
||||||
|
public Destination createIfAbsent() throws I2PException, IOException, DataFormatException {
|
||||||
if(!this.file.exists()) {
|
if(!this.file.exists()) {
|
||||||
FileOutputStream out = new FileOutputStream(this.file);
|
FileOutputStream out = new FileOutputStream(this.file);
|
||||||
this.dest = this.client.createDestination(out);
|
this.client.createDestination(out);
|
||||||
out.close();
|
out.close();
|
||||||
}
|
}
|
||||||
|
return getDestination();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Destination getDestination() {
|
/** Also sets the local privKay and signingPrivKey */
|
||||||
// TODO: how to load destination if this is an old key?
|
public Destination getDestination() throws I2PSessionException, IOException, DataFormatException {
|
||||||
return dest;
|
if (dest == null) {
|
||||||
|
I2PSession s = open();
|
||||||
|
if (s != null) {
|
||||||
|
this.dest = new VerifiedDestination(s.getMyDestination());
|
||||||
|
this.privKey = s.getDecryptionKey();
|
||||||
|
this.signingPrivKey = s.getPrivateKey();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return this.dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrivateKey getPrivKey() { return this.privKey; }
|
||||||
|
public SigningPrivateKey getSigningPrivKey() { return this.signingPrivKey; }
|
||||||
|
|
||||||
public I2PSession open() throws I2PSessionException, IOException {
|
public I2PSession open() throws I2PSessionException, IOException {
|
||||||
return this.open(new Properties());
|
return this.open(new Properties());
|
||||||
@ -50,10 +90,205 @@ public class PrivateKeyFile {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copied from I2PClientImpl.createDestination()
|
||||||
|
*/
|
||||||
|
public void write() throws IOException, DataFormatException {
|
||||||
|
FileOutputStream out = new FileOutputStream(this.file);
|
||||||
|
this.dest.writeBytes(out);
|
||||||
|
this.privKey.writeBytes(out);
|
||||||
|
this.signingPrivKey.writeBytes(out);
|
||||||
|
out.flush();
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
StringBuffer s = new StringBuffer(128);
|
||||||
|
s.append("Dest: ");
|
||||||
|
s.append(this.dest.toBase64());
|
||||||
|
s.append("\nContains: ");
|
||||||
|
s.append(this.dest);
|
||||||
|
s.append("\nPrivate Key: ");
|
||||||
|
s.append(this.privKey);
|
||||||
|
s.append("\nSigining Private Key: ");
|
||||||
|
s.append(this.signingPrivKey);
|
||||||
|
s.append("\n");
|
||||||
|
return s.toString();
|
||||||
|
}
|
||||||
|
|
||||||
private File file;
|
private File file;
|
||||||
private I2PClient client;
|
private I2PClient client;
|
||||||
private Destination dest;
|
private Destination dest;
|
||||||
|
private PrivateKey privKey;
|
||||||
|
private SigningPrivateKey signingPrivKey;
|
||||||
|
|
||||||
|
private static final int HASH_EFFORT = VerifiedDestination.MIN_HASHCASH_EFFORT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new PrivateKeyFile, or modify an existing one, with various
|
||||||
|
* types of Certificates.
|
||||||
|
*
|
||||||
|
* Changing a Certificate does not change the public or private keys.
|
||||||
|
* But it does change the Destination Hash, which effectively makes it
|
||||||
|
* a new Destination. In other words, don't change the Certificate on
|
||||||
|
* a Destination you've already registered in a hosts.txt key add form.
|
||||||
|
*
|
||||||
|
* Copied and expanded from that in Destination.java
|
||||||
|
*/
|
||||||
|
public static void main(String args[]) {
|
||||||
|
if (args.length == 0) {
|
||||||
|
System.err.println("Usage: PrivateKeyFile filename (generates if nonexistent, then prints)");
|
||||||
|
System.err.println(" PrivateKeyFile -h filename (generates if nonexistent, adds hashcash cert)");
|
||||||
|
System.err.println(" PrivateKeyFile -h effort filename (specify HashCash effort instead of default " + HASH_EFFORT + ")");
|
||||||
|
System.err.println(" PrivateKeyFile -n filename (changes to null cert)");
|
||||||
|
System.err.println(" PrivateKeyFile -s filename signwithdestfile (generates if nonexistent, adds cert signed by 2nd dest)");
|
||||||
|
System.err.println(" PrivateKeyFile -u filename (changes to unknown cert)");
|
||||||
|
System.err.println(" PrivateKeyFile -x filename (changes to hidden cert)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
I2PClient client = I2PClientFactory.createClient();
|
||||||
|
|
||||||
|
int filearg = 0;
|
||||||
|
if (args.length > 1) {
|
||||||
|
if (args.length >= 2 && args[0].equals("-h"))
|
||||||
|
filearg = args.length - 1;
|
||||||
|
else
|
||||||
|
filearg = 1;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
File f = new File(args[filearg]);
|
||||||
|
PrivateKeyFile pkf = new PrivateKeyFile(f, client);
|
||||||
|
Destination d = pkf.createIfAbsent();
|
||||||
|
System.out.println("Original Destination:");
|
||||||
|
System.out.println(pkf);
|
||||||
|
verifySignature(d);
|
||||||
|
if (args.length == 1)
|
||||||
|
return;
|
||||||
|
Certificate c = new Certificate();
|
||||||
|
if (args[0].equals("-n")) {
|
||||||
|
// Cert constructor generates a null cert
|
||||||
|
} else if (args[0].equals("-u")) {
|
||||||
|
c.setCertificateType(99);
|
||||||
|
} else if (args[0].equals("-x")) {
|
||||||
|
c.setCertificateType(Certificate.CERTIFICATE_TYPE_HIDDEN);
|
||||||
|
} else if (args[0].equals("-h")) {
|
||||||
|
int hashEffort = HASH_EFFORT;
|
||||||
|
if (args.length == 3)
|
||||||
|
hashEffort = Integer.parseInt(args[1]);
|
||||||
|
System.out.println("Estimating hashcash generation time, stand by...");
|
||||||
|
// takes a lot longer than the estimate usually...
|
||||||
|
// maybe because the resource string is much longer than used in the estimate?
|
||||||
|
long low = HashCash.estimateTime(hashEffort);
|
||||||
|
System.out.println("It is estimated this will take " + DataHelper.formatDuration(low) +
|
||||||
|
" to " + DataHelper.formatDuration(4*low));
|
||||||
|
|
||||||
|
long begin = System.currentTimeMillis();
|
||||||
|
System.out.println("Starting hashcash generation now...");
|
||||||
|
String resource = d.getPublicKey().toBase64() + d.getSigningPublicKey().toBase64();
|
||||||
|
HashCash hc = HashCash.mintCash(resource, hashEffort);
|
||||||
|
System.out.println("Generation took: " + DataHelper.formatDuration(System.currentTimeMillis() - begin));
|
||||||
|
System.out.println("Full Hashcash is: " + hc);
|
||||||
|
// Take the resource out of the stamp
|
||||||
|
String hcs = hc.toString();
|
||||||
|
int end1 = 0;
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
end1 = 1 + hcs.indexOf(':', end1);
|
||||||
|
if (end1 < 0) {
|
||||||
|
System.out.println("Bad hashcash");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int start2 = hcs.indexOf(':', end1);
|
||||||
|
if (start2 < 0) {
|
||||||
|
System.out.println("Bad hashcash");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hcs = hcs.substring(0, end1) + hcs.substring(start2);
|
||||||
|
System.out.println("Short Hashcash is: " + hcs);
|
||||||
|
|
||||||
|
c.setCertificateType(Certificate.CERTIFICATE_TYPE_HASHCASH);
|
||||||
|
c.setPayload(hcs.getBytes());
|
||||||
|
} else if (args.length == 3 && args[0].equals("-s")) {
|
||||||
|
// Sign dest1 with dest2's Signing Private Key
|
||||||
|
File f2 = new File(args[2]);
|
||||||
|
I2PClient client2 = I2PClientFactory.createClient();
|
||||||
|
PrivateKeyFile pkf2 = new PrivateKeyFile(f2, client2);
|
||||||
|
Destination d2 = pkf2.getDestination();
|
||||||
|
SigningPrivateKey spk2 = pkf2.getSigningPrivKey();
|
||||||
|
System.out.println("Signing With Dest:");
|
||||||
|
System.out.println(pkf2.toString());
|
||||||
|
|
||||||
|
int len = PublicKey.KEYSIZE_BYTES + SigningPublicKey.KEYSIZE_BYTES; // no cert
|
||||||
|
byte[] data = new byte[len];
|
||||||
|
System.arraycopy(d.getPublicKey().getData(), 0, data, 0, PublicKey.KEYSIZE_BYTES);
|
||||||
|
System.arraycopy(d.getSigningPublicKey().getData(), 0, data, PublicKey.KEYSIZE_BYTES, SigningPublicKey.KEYSIZE_BYTES);
|
||||||
|
byte[] payload = new byte[Hash.HASH_LENGTH + Signature.SIGNATURE_BYTES];
|
||||||
|
byte[] sig = DSAEngine.getInstance().sign(new ByteArrayInputStream(data), spk2).getData();
|
||||||
|
System.arraycopy(sig, 0, payload, 0, Signature.SIGNATURE_BYTES);
|
||||||
|
// Add dest2's Hash for reference
|
||||||
|
byte[] h2 = d2.calculateHash().getData();
|
||||||
|
System.arraycopy(h2, 0, payload, Signature.SIGNATURE_BYTES, Hash.HASH_LENGTH);
|
||||||
|
c.setCertificateType(Certificate.CERTIFICATE_TYPE_SIGNED);
|
||||||
|
c.setPayload(payload);
|
||||||
|
}
|
||||||
|
d.setCertificate(c); // do this rather than just change the existing cert so the hash is recalculated
|
||||||
|
System.out.println("New signed destination is:");
|
||||||
|
System.out.println(pkf);
|
||||||
|
pkf.write();
|
||||||
|
verifySignature(d);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sample code to verify a 3rd party signature.
|
||||||
|
* This just goes through all the hosts.txt files and tries everybody.
|
||||||
|
* You need to be in the $I2P directory or have a local hosts.txt for this to work.
|
||||||
|
* Doubt this is what you want as it is super-slow, and what good is
|
||||||
|
* a signing scheme where anybody is allowed to sign?
|
||||||
|
*
|
||||||
|
* In a real application you would make a list of approved signers,
|
||||||
|
* do a naming lookup to get their Destinations, and try those only.
|
||||||
|
* Or do a netDb lookup of the Hash in the Certificate, do a reverse
|
||||||
|
* naming lookup to see if it is allowed, then verify the Signature.
|
||||||
|
*/
|
||||||
|
public static boolean verifySignature(Destination d) {
|
||||||
|
if (d.getCertificate().getCertificateType() != Certificate.CERTIFICATE_TYPE_SIGNED)
|
||||||
|
return false;
|
||||||
|
int len = PublicKey.KEYSIZE_BYTES + SigningPublicKey.KEYSIZE_BYTES; // no cert
|
||||||
|
byte[] data = new byte[len];
|
||||||
|
System.arraycopy(d.getPublicKey().getData(), 0, data, 0, PublicKey.KEYSIZE_BYTES);
|
||||||
|
System.arraycopy(d.getSigningPublicKey().getData(), 0, data, PublicKey.KEYSIZE_BYTES, SigningPublicKey.KEYSIZE_BYTES);
|
||||||
|
Signature sig = new Signature(d.getCertificate().getPayload());
|
||||||
|
|
||||||
|
String[] filenames = new String[] {"privatehosts.txt", "userhosts.txt", "hosts.txt"};
|
||||||
|
for (int i = 0; i < filenames.length; i++) {
|
||||||
|
Properties hosts = new Properties();
|
||||||
|
try {
|
||||||
|
File f = new File(filenames[i]);
|
||||||
|
if ( (f.exists()) && (f.canRead()) ) {
|
||||||
|
DataHelper.loadProps(hosts, f, true);
|
||||||
|
|
||||||
|
for (Iterator iter = hosts.entrySet().iterator(); iter.hasNext(); ) {
|
||||||
|
Map.Entry entry = (Map.Entry)iter.next();
|
||||||
|
String s = (String) entry.getValue();
|
||||||
|
Destination signer = new Destination(s);
|
||||||
|
if (checkSignature(sig, data, signer.getSigningPublicKey())) {
|
||||||
|
System.out.println("Good signature from: " + entry.getKey());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ioe) {
|
||||||
|
}
|
||||||
|
// not found, continue to the next file
|
||||||
|
}
|
||||||
|
System.out.println("No valid signer found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean checkSignature(Signature s, byte[] data, SigningPublicKey spk) {
|
||||||
|
return DSAEngine.getInstance().verifySignature(s, data, spk);
|
||||||
|
}
|
||||||
}
|
}
|
163
core/java/src/net/i2p/data/VerifiedDestination.java
Normal file
163
core/java/src/net/i2p/data/VerifiedDestination.java
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
package net.i2p.data;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* free (adj.): unencumbered; not under the control of others
|
||||||
|
* Written by jrandom in 2003 and released into the public domain
|
||||||
|
* with no warranty of any kind, either expressed or implied.
|
||||||
|
* It probably won't make your computer catch on fire, or eat
|
||||||
|
* your children, but it might. Use at your own risk.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import com.nettgryppa.security.HashCash;
|
||||||
|
|
||||||
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend Destination with methods to verify its Certificate.
|
||||||
|
* The router does not check Certificates, it doesn't care.
|
||||||
|
* Apps however (particularly addressbook) may wish to enforce various
|
||||||
|
* cert content, format, and policies.
|
||||||
|
* This class is written such that apps may extend it to
|
||||||
|
* create their own policies.
|
||||||
|
*
|
||||||
|
* @author zzz
|
||||||
|
*/
|
||||||
|
public class VerifiedDestination extends Destination {
|
||||||
|
protected final static Log _log = new Log(Destination.class);
|
||||||
|
|
||||||
|
public VerifiedDestination() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* alternative constructor which takes a base64 string representation
|
||||||
|
* @param s a Base64 representation of the destination, as (eg) is used in hosts.txt
|
||||||
|
*/
|
||||||
|
public VerifiedDestination(String s) throws DataFormatException {
|
||||||
|
this();
|
||||||
|
fromBase64(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create from an existing Dest
|
||||||
|
* @param d must be non-null
|
||||||
|
*/
|
||||||
|
public VerifiedDestination(Destination d) throws DataFormatException {
|
||||||
|
this(d.toBase64());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* verify the certificate.
|
||||||
|
* @param allowNone If true, allow a NULL or HIDDEN certificate.
|
||||||
|
*/
|
||||||
|
public boolean verifyCert(boolean allowNone) {
|
||||||
|
if (_publicKey == null || _signingKey == null || _certificate == null)
|
||||||
|
return false;
|
||||||
|
switch (_certificate.getCertificateType()) {
|
||||||
|
case Certificate.CERTIFICATE_TYPE_NULL:
|
||||||
|
case Certificate.CERTIFICATE_TYPE_HIDDEN:
|
||||||
|
return allowNone;
|
||||||
|
case Certificate.CERTIFICATE_TYPE_HASHCASH:
|
||||||
|
return verifyHashCashCert();
|
||||||
|
case Certificate.CERTIFICATE_TYPE_SIGNED:
|
||||||
|
return verifySignedCert();
|
||||||
|
}
|
||||||
|
return verifyUnknownCert();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Defaults for HashCash Certs */
|
||||||
|
public final static int MIN_HASHCASH_EFFORT = 20;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HashCash Certs are used to demonstrate proof-of-work.
|
||||||
|
*
|
||||||
|
* We define a HashCash Certificate as follows:
|
||||||
|
* - length: typically 47 bytes, but may vary somewhat
|
||||||
|
* - contents: A version 1 HashCash Stamp,
|
||||||
|
* defined at http://www.hashcash.org/docs/hashcash.html#stamp_format__version_1_
|
||||||
|
* modified to remove the contents of the 4th field (the resource)
|
||||||
|
* original is ver:bits:date:resource:[ext]:rand:counter
|
||||||
|
* I2P version is ver:bits:date::[ext]:rand:counter
|
||||||
|
* The HashCash is calculated with the following resource:
|
||||||
|
* The Base64 of the Public Key concatenated with the Base64 of the Signing Public Key
|
||||||
|
* (NOT the Base64 of the concatenated keys)
|
||||||
|
* To generate a Cert of this type, see PrivateKeyFile.main()
|
||||||
|
* To verify, we must put the keys back into the resource field of the stamp,
|
||||||
|
* then pass it to the HashCash constructor, then get the number of leading
|
||||||
|
* zeros and see if it meets our minimum effort.
|
||||||
|
*/
|
||||||
|
protected boolean verifyHashCashCert() {
|
||||||
|
String hcs = new String(_certificate.getPayload());
|
||||||
|
int end1 = 0;
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
end1 = 1 + hcs.indexOf(':', end1);
|
||||||
|
if (end1 < 0)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int start2 = hcs.indexOf(':', end1);
|
||||||
|
if (start2 < 0)
|
||||||
|
return false;
|
||||||
|
// put the keys back into the 4th field of the stamp
|
||||||
|
hcs = hcs.substring(0, end1) + _publicKey.toBase64() + _signingKey.toBase64() + hcs.substring(start2);
|
||||||
|
HashCash hc;
|
||||||
|
try {
|
||||||
|
hc = new HashCash(hcs);
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
return false;
|
||||||
|
} catch (NoSuchAlgorithmException nsae) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return hc.getValue() >= MIN_HASHCASH_EFFORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Defaults for Signed Certs */
|
||||||
|
public final static int CERTIFICATE_LENGTH_SIGNED = Signature.SIGNATURE_BYTES;
|
||||||
|
public final static int CERTIFICATE_LENGTH_SIGNED_WITH_HASH = Signature.SIGNATURE_BYTES + Hash.HASH_LENGTH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signed Certs are signed by a 3rd-party Destination.
|
||||||
|
* They can be used for a second-level domain, for example, to sign the
|
||||||
|
* Destination for a third-level domain. Or for a central authority
|
||||||
|
* to approve a destination.
|
||||||
|
*
|
||||||
|
* We define a Signed Certificate as follows:
|
||||||
|
* - length: Either 44 or 72 bytes
|
||||||
|
* - contents:
|
||||||
|
* 1: a 44 byte Signature
|
||||||
|
* 2 (optional): a 32 byte Hash of the signing Destination
|
||||||
|
* This can be a hint to the verification process to help find
|
||||||
|
* the identity and keys of the signing Destination.
|
||||||
|
* Data which is signed: The first 384 bytes of the Destination
|
||||||
|
* (i.e. the Public Key and Signing Public Key, WITHOUT the Certificate)
|
||||||
|
*
|
||||||
|
* It is not appropriate to enforce a particular delegation scheme here.
|
||||||
|
* The application will need to apply additional steps to select
|
||||||
|
* an appropriate signing Destination and verify the signature.
|
||||||
|
*
|
||||||
|
* See PrivateKeyFile.verifySignature() for sample verification code.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected boolean verifySignedCert() {
|
||||||
|
return _certificate.getPayload() != null &&
|
||||||
|
(_certificate.getPayload().length == CERTIFICATE_LENGTH_SIGNED ||
|
||||||
|
_certificate.getPayload().length == CERTIFICATE_LENGTH_SIGNED_WITH_HASH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reject all unknown certs
|
||||||
|
*/
|
||||||
|
protected boolean verifyUnknownCert() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
StringBuffer buf = new StringBuffer(128);
|
||||||
|
buf.append(super.toString());
|
||||||
|
buf.append("\n\tVerified Certificate? ").append(verifyCert(true));
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
history.txt
20
history.txt
@ -1,4 +1,22 @@
|
|||||||
2008-10-27 zzz
|
2008-11-02 zzz
|
||||||
|
* Certificates:
|
||||||
|
- Add a signed Certificate type
|
||||||
|
- Add a main() to PrivateKeyFile to generate
|
||||||
|
Destinations with various Certificate types
|
||||||
|
- Add a VerifiedDestination class to check Certificates
|
||||||
|
of various types
|
||||||
|
- Add a HashCash library from http://www.nettgryppa.com/code/
|
||||||
|
(no distribution restrictions)
|
||||||
|
- Allow non-null Certificates in addressbook
|
||||||
|
* I2PTunnel: Move some wayward stats to the I2PTunnel group
|
||||||
|
* NamingServices: Implement caching in the abstract class
|
||||||
|
* NewsFetcher: Fix last updated time
|
||||||
|
* Streaming: Increase MTU to 1730 (was 960);
|
||||||
|
see ConnectionOptions.java for analysis
|
||||||
|
* Throttle: Reduce default max tunnels to 2000 (was 2500)
|
||||||
|
* clients.config: Disable SAM and BOB by default for new installs
|
||||||
|
|
||||||
|
2008-10-26 zzz
|
||||||
* config.jsp: Add more help
|
* config.jsp: Add more help
|
||||||
* peers.jsp: Clean up 'Listening on' formatting
|
* peers.jsp: Clean up 'Listening on' formatting
|
||||||
* profiles.jsp: Don't override locale number format
|
* profiles.jsp: Don't override locale number format
|
||||||
|
@ -17,7 +17,7 @@ import net.i2p.CoreVersion;
|
|||||||
public class RouterVersion {
|
public class RouterVersion {
|
||||||
public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $";
|
public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $";
|
||||||
public final static String VERSION = "0.6.4";
|
public final static String VERSION = "0.6.4";
|
||||||
public final static long BUILD = 7;
|
public final static long BUILD = 8;
|
||||||
public static void main(String args[]) {
|
public static void main(String args[]) {
|
||||||
System.out.println("I2P Router version: " + VERSION + "-" + BUILD);
|
System.out.println("I2P Router version: " + VERSION + "-" + BUILD);
|
||||||
System.out.println("Router ID: " + RouterVersion.ID);
|
System.out.println("Router ID: " + RouterVersion.ID);
|
||||||
|
Reference in New Issue
Block a user