forked from I2P_Developers/i2p.i2p
I2CP, Data: Initial support for LS2 offline keys in I2PSession and PrivateKeyFile
This commit is contained in:
@ -19,7 +19,9 @@ import net.i2p.data.Hash;
|
|||||||
import net.i2p.data.PrivateKey;
|
import net.i2p.data.PrivateKey;
|
||||||
import net.i2p.data.SessionKey;
|
import net.i2p.data.SessionKey;
|
||||||
import net.i2p.data.SessionTag;
|
import net.i2p.data.SessionTag;
|
||||||
|
import net.i2p.data.Signature;
|
||||||
import net.i2p.data.SigningPrivateKey;
|
import net.i2p.data.SigningPrivateKey;
|
||||||
|
import net.i2p.data.SigningPublicKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Define the standard means of sending and receiving messages on the
|
* <p>Define the standard means of sending and receiving messages on the
|
||||||
@ -300,10 +302,36 @@ public interface I2PSession {
|
|||||||
public PrivateKey getDecryptionKey();
|
public PrivateKey getDecryptionKey();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the signing SigningPrivateKey associated with the Destination
|
* Retrieve the signing SigningPrivateKey associated with the Destination.
|
||||||
|
* As of 0.9.38, this will be the transient key if offline signed.
|
||||||
*/
|
*/
|
||||||
public SigningPrivateKey getPrivateKey();
|
public SigningPrivateKey getPrivateKey();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does this session have offline and transient keys?
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
public boolean isOffline();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the offline expiration
|
||||||
|
* @return Java time (ms) or 0 if not initialized or does not have offline keys
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
public long getOfflineExpiration();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return null on error or if not initialized or does not have offline keys
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
public Signature getOfflineSignature();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return null on error or if not initialized or does not have offline keys
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
public SigningPublicKey getTransientSigningPublicKey();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lookup a Destination by Hash.
|
* Lookup a Destination by Hash.
|
||||||
* Blocking. Waits a max of 10 seconds by default.
|
* Blocking. Waits a max of 10 seconds by default.
|
||||||
|
@ -36,13 +36,17 @@ import net.i2p.client.I2PClient;
|
|||||||
import net.i2p.client.I2PSession;
|
import net.i2p.client.I2PSession;
|
||||||
import net.i2p.client.I2PSessionException;
|
import net.i2p.client.I2PSessionException;
|
||||||
import net.i2p.client.I2PSessionListener;
|
import net.i2p.client.I2PSessionListener;
|
||||||
|
import net.i2p.crypto.SigType;
|
||||||
import net.i2p.data.Base32;
|
import net.i2p.data.Base32;
|
||||||
import net.i2p.data.DataFormatException;
|
import net.i2p.data.DataFormatException;
|
||||||
|
import net.i2p.data.DataHelper;
|
||||||
import net.i2p.data.Destination;
|
import net.i2p.data.Destination;
|
||||||
import net.i2p.data.Hash;
|
import net.i2p.data.Hash;
|
||||||
import net.i2p.data.LeaseSet;
|
import net.i2p.data.LeaseSet;
|
||||||
import net.i2p.data.PrivateKey;
|
import net.i2p.data.PrivateKey;
|
||||||
|
import net.i2p.data.Signature;
|
||||||
import net.i2p.data.SigningPrivateKey;
|
import net.i2p.data.SigningPrivateKey;
|
||||||
|
import net.i2p.data.SigningPublicKey;
|
||||||
import net.i2p.data.i2cp.DestLookupMessage;
|
import net.i2p.data.i2cp.DestLookupMessage;
|
||||||
import net.i2p.data.i2cp.DestReplyMessage;
|
import net.i2p.data.i2cp.DestReplyMessage;
|
||||||
import net.i2p.data.i2cp.GetBandwidthLimitsMessage;
|
import net.i2p.data.i2cp.GetBandwidthLimitsMessage;
|
||||||
@ -91,6 +95,9 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
|||||||
private SessionId _sessionId;
|
private SessionId _sessionId;
|
||||||
/** currently granted lease set, or null */
|
/** currently granted lease set, or null */
|
||||||
protected volatile LeaseSet _leaseSet;
|
protected volatile LeaseSet _leaseSet;
|
||||||
|
private long _offlineExpiration;
|
||||||
|
private Signature _offlineSignature;
|
||||||
|
protected SigningPublicKey _transientSigningPublicKey;
|
||||||
|
|
||||||
// subsession stuff
|
// subsession stuff
|
||||||
// registered subsessions
|
// registered subsessions
|
||||||
@ -301,6 +308,9 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
|||||||
*
|
*
|
||||||
* As of 0.9.19, defaults in options are honored.
|
* As of 0.9.19, defaults in options are honored.
|
||||||
*
|
*
|
||||||
|
* This does NOT validate consistency of the destKeyStream,
|
||||||
|
* e.g. pubkey/privkey match or valid offline sig. The router does that.
|
||||||
|
*
|
||||||
* @param destKeyStream stream containing the private key data,
|
* @param destKeyStream stream containing the private key data,
|
||||||
* format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
|
* format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
|
||||||
* @param options set of options to configure the router with, if null will use System properties
|
* @param options set of options to configure the router with, if null will use System properties
|
||||||
@ -548,7 +558,11 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load up the destKeyFile for our Destination, PrivateKey, and SigningPrivateKey
|
* Load up the destKeyFile for our Destination, PrivateKey, and SigningPrivateKey.
|
||||||
|
* As of 0.9.38, loads the offline data also. See PrivateKeyFile.
|
||||||
|
*
|
||||||
|
* This does NOT validate consistency of the destKeyStream,
|
||||||
|
* e.g. pubkey/privkey match or valid offline sig. The router does that.
|
||||||
*
|
*
|
||||||
* @throws DataFormatException if the file is in the wrong format or keys are invalid
|
* @throws DataFormatException if the file is in the wrong format or keys are invalid
|
||||||
* @throws IOException if there is a problem reading the file
|
* @throws IOException if there is a problem reading the file
|
||||||
@ -556,8 +570,68 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
|||||||
private void readDestination(InputStream destKeyStream) throws DataFormatException, IOException {
|
private void readDestination(InputStream destKeyStream) throws DataFormatException, IOException {
|
||||||
_myDestination.readBytes(destKeyStream);
|
_myDestination.readBytes(destKeyStream);
|
||||||
_privateKey.readBytes(destKeyStream);
|
_privateKey.readBytes(destKeyStream);
|
||||||
_signingPrivateKey = new SigningPrivateKey(_myDestination.getSigningPublicKey().getType());
|
SigType dtype = _myDestination.getSigningPublicKey().getType();
|
||||||
|
_signingPrivateKey = new SigningPrivateKey(dtype);
|
||||||
_signingPrivateKey.readBytes(destKeyStream);
|
_signingPrivateKey.readBytes(destKeyStream);
|
||||||
|
if (isOffline(_signingPrivateKey)) {
|
||||||
|
_offlineExpiration = DataHelper.readLong(destKeyStream, 4) * 1000;;
|
||||||
|
int itype = (int) DataHelper.readLong(destKeyStream, 2);
|
||||||
|
SigType type = SigType.getByCode(itype);
|
||||||
|
if (type == null)
|
||||||
|
throw new DataFormatException("Unsupported transient sig type: " + itype);
|
||||||
|
_transientSigningPublicKey = new SigningPublicKey(type);
|
||||||
|
_transientSigningPublicKey.readBytes(destKeyStream);
|
||||||
|
_offlineSignature = new Signature(dtype);
|
||||||
|
_offlineSignature.readBytes(destKeyStream);
|
||||||
|
// replace SPK
|
||||||
|
_signingPrivateKey = new SigningPrivateKey(type);
|
||||||
|
_signingPrivateKey.readBytes(destKeyStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constant time
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
private static boolean isOffline(SigningPrivateKey spk) {
|
||||||
|
byte b = 0;
|
||||||
|
byte[] data = spk.getData();
|
||||||
|
for (int i = 0; i < data.length; i++) {
|
||||||
|
b |= data[i];
|
||||||
|
}
|
||||||
|
return b == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does this session have offline and transient keys?
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
public boolean isOffline() {
|
||||||
|
return _offlineSignature != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Java time (ms) or 0 if not initialized or does not have offline keys
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
public long getOfflineExpiration() {
|
||||||
|
return _offlineExpiration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return null on error or if not initialized or does not have offline keys
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
public Signature getOfflineSignature() {
|
||||||
|
return _offlineSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return null on error or if not initialized or does not have offline keys
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
public SigningPublicKey getTransientSigningPublicKey() {
|
||||||
|
return _transientSigningPublicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1044,7 +1118,8 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
|||||||
public PrivateKey getDecryptionKey() { return _privateKey; }
|
public PrivateKey getDecryptionKey() { return _privateKey; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the signing SigningPrivateKey
|
* Retrieve the signing SigningPrivateKey.
|
||||||
|
* As of 0.9.38, this will be the transient key if offline signed.
|
||||||
*/
|
*/
|
||||||
public SigningPrivateKey getPrivateKey() { return _signingPrivateKey; }
|
public SigningPrivateKey getPrivateKey() { return _signingPrivateKey; }
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import java.io.OutputStream;
|
|||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
@ -45,6 +46,14 @@ import net.i2p.util.SecureFileOutputStream;
|
|||||||
* - Certificate if length != 0
|
* - Certificate if length != 0
|
||||||
* - Private key (256 bytes)
|
* - Private key (256 bytes)
|
||||||
* - Signing Private key (20 bytes, or length specified by key certificate)
|
* - Signing Private key (20 bytes, or length specified by key certificate)
|
||||||
|
* - As of 0.9.38, if the Signing Private Key is all zeros,
|
||||||
|
* the offline signature section (see proposal 123):
|
||||||
|
* - Expires timestamp (4 bytes, seconds since epoch, rolls over in 2106)
|
||||||
|
* - Sig type of transient public key (2 bytes)
|
||||||
|
* - Transient Signing Public key (length as specified by transient sig type)
|
||||||
|
* - Signature of Signed Public key by offline key (length as specified by destination sig type)
|
||||||
|
* - Transient Signing Private key (length as specified by transient sig type)
|
||||||
|
*
|
||||||
* Total: 663 or more bytes
|
* Total: 663 or more bytes
|
||||||
*</pre>
|
*</pre>
|
||||||
*
|
*
|
||||||
@ -60,6 +69,10 @@ public class PrivateKeyFile {
|
|||||||
protected Destination dest;
|
protected Destination dest;
|
||||||
protected PrivateKey privKey;
|
protected PrivateKey privKey;
|
||||||
protected SigningPrivateKey signingPrivKey;
|
protected SigningPrivateKey signingPrivKey;
|
||||||
|
private long _offlineExpiration;
|
||||||
|
private Signature _offlineSignature;
|
||||||
|
private SigningPrivateKey _transientSigningPrivKey;
|
||||||
|
private SigningPublicKey _transientSigningPubKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new PrivateKeyFile, or modify an existing one, with various
|
* Create a new PrivateKeyFile, or modify an existing one, with various
|
||||||
@ -75,10 +88,13 @@ public class PrivateKeyFile {
|
|||||||
public static void main(String args[]) {
|
public static void main(String args[]) {
|
||||||
int hashEffort = HASH_EFFORT;
|
int hashEffort = HASH_EFFORT;
|
||||||
String stype = null;
|
String stype = null;
|
||||||
|
String ttype = null;
|
||||||
String hostname = null;
|
String hostname = null;
|
||||||
|
String offline = null;
|
||||||
|
int days = 365;
|
||||||
int mode = 0;
|
int mode = 0;
|
||||||
boolean error = false;
|
boolean error = false;
|
||||||
Getopt g = new Getopt("pkf", args, "t:nuxhse:c:a:");
|
Getopt g = new Getopt("pkf", args, "t:nuxhse:c:a:o:d:r:");
|
||||||
int c;
|
int c;
|
||||||
while ((c = g.getopt()) != -1) {
|
while ((c = g.getopt()) != -1) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
@ -109,10 +125,26 @@ public class PrivateKeyFile {
|
|||||||
error = true;
|
error = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'o':
|
||||||
|
offline = g.getOptarg();
|
||||||
|
if (mode == 0)
|
||||||
|
mode = c;
|
||||||
|
else
|
||||||
|
error = true;
|
||||||
|
break;
|
||||||
|
|
||||||
case 'e':
|
case 'e':
|
||||||
hashEffort = Integer.parseInt(g.getOptarg());
|
hashEffort = Integer.parseInt(g.getOptarg());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'd':
|
||||||
|
days = Integer.parseInt(g.getOptarg());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'r':
|
||||||
|
ttype = g.getOptarg();
|
||||||
|
break;
|
||||||
|
|
||||||
case '?':
|
case '?':
|
||||||
case ':':
|
case ':':
|
||||||
default:
|
default:
|
||||||
@ -132,7 +164,8 @@ public class PrivateKeyFile {
|
|||||||
I2PClient client = I2PClientFactory.createClient();
|
I2PClient client = I2PClientFactory.createClient();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
File f = new File(filearg);
|
String orig = offline != null ? offline : filearg;
|
||||||
|
File f = new File(orig);
|
||||||
boolean exists = f.exists();
|
boolean exists = f.exists();
|
||||||
PrivateKeyFile pkf = new PrivateKeyFile(f, client);
|
PrivateKeyFile pkf = new PrivateKeyFile(f, client);
|
||||||
Destination d;
|
Destination d;
|
||||||
@ -179,13 +212,16 @@ public class PrivateKeyFile {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 's':
|
case 's':
|
||||||
|
{
|
||||||
// Sign dest1 with dest2's Signing Private Key
|
// Sign dest1 with dest2's Signing Private Key
|
||||||
PrivateKeyFile pkf2 = new PrivateKeyFile(args[g.getOptind() + 1]);
|
PrivateKeyFile pkf2 = new PrivateKeyFile(args[g.getOptind() + 1]);
|
||||||
pkf.setSignedCert(pkf2);
|
pkf.setSignedCert(pkf2);
|
||||||
System.out.println("New destination with signed cert is:");
|
System.out.println("New destination with signed cert is:");
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 't':
|
case 't':
|
||||||
|
{
|
||||||
// KeyCert
|
// KeyCert
|
||||||
SigType type = SigType.parseSigType(stype);
|
SigType type = SigType.parseSigType(stype);
|
||||||
if (type == null)
|
if (type == null)
|
||||||
@ -193,8 +229,10 @@ public class PrivateKeyFile {
|
|||||||
pkf.setKeyCert(type);
|
pkf.setKeyCert(type);
|
||||||
System.out.println("New destination with key cert is:");
|
System.out.println("New destination with key cert is:");
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'a':
|
case 'a':
|
||||||
|
{
|
||||||
// addressbook auth
|
// addressbook auth
|
||||||
OrderedProperties props = new OrderedProperties();
|
OrderedProperties props = new OrderedProperties();
|
||||||
HostTxtEntry he = new HostTxtEntry(hostname, d.toBase64(), props);
|
HostTxtEntry he = new HostTxtEntry(hostname, d.toBase64(), props);
|
||||||
@ -205,6 +243,50 @@ public class PrivateKeyFile {
|
|||||||
out.flush();
|
out.flush();
|
||||||
System.out.println("");
|
System.out.println("");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'o':
|
||||||
|
{
|
||||||
|
// Sign dest1 with dest2's Signing Private Key
|
||||||
|
File f3 = new File(filearg);
|
||||||
|
// set dummy SPK
|
||||||
|
SigType type = pkf.getSigningPrivKey().getType();
|
||||||
|
byte[] dbytes = new byte[type.getPrivkeyLen()];
|
||||||
|
SigningPrivateKey dummy = new SigningPrivateKey(type, dbytes);
|
||||||
|
PrivateKeyFile pkf2 = new PrivateKeyFile(f3, pkf.getDestination(), pkf.getPrivKey(), dummy);
|
||||||
|
// keygen transient
|
||||||
|
SigType tstype = SigType.EdDSA_SHA512_Ed25519;
|
||||||
|
if (ttype != null) {
|
||||||
|
tstype = SigType.parseSigType(ttype);
|
||||||
|
if (tstype == null)
|
||||||
|
throw new I2PException("Bad or unsupported -r option: " + ttype);
|
||||||
|
}
|
||||||
|
SimpleDataStructure signingKeys[];
|
||||||
|
try {
|
||||||
|
signingKeys = KeyGenerator.getInstance().generateSigningKeys(tstype);
|
||||||
|
} catch (GeneralSecurityException gse) {
|
||||||
|
throw new RuntimeException("keygen fail", gse);
|
||||||
|
}
|
||||||
|
SigningPublicKey tSigningPubKey = (SigningPublicKey) signingKeys[0];
|
||||||
|
SigningPrivateKey tSigningPrivKey = (SigningPrivateKey) signingKeys[1];
|
||||||
|
// set expires
|
||||||
|
long expires = System.currentTimeMillis() + (days * 24*60*60*1000L);
|
||||||
|
// sign
|
||||||
|
byte[] data = new byte[4 + 2 + tSigningPubKey.length()];
|
||||||
|
DataHelper.toLong(data, 0, 4, expires / 1000);
|
||||||
|
DataHelper.toLong(data, 4, 2, tstype.getCode());
|
||||||
|
System.arraycopy(tSigningPubKey.getData(), 0, data, 6, tSigningPubKey.length());
|
||||||
|
Signature sign = DSAEngine.getInstance().sign(data, pkf.getSigningPrivKey());
|
||||||
|
if (sign == null)
|
||||||
|
throw new I2PException("Sig fail");
|
||||||
|
// set the offline block
|
||||||
|
pkf2.setOfflineData(expires, tSigningPubKey, sign, tSigningPrivKey);
|
||||||
|
// write it
|
||||||
|
System.out.println("New destination with offline signature is:");
|
||||||
|
System.out.println(pkf2);
|
||||||
|
pkf2.write();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// shouldn't happen
|
// shouldn't happen
|
||||||
@ -226,15 +308,22 @@ public class PrivateKeyFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void usage() {
|
private static void usage() {
|
||||||
System.err.println("Usage: PrivateKeyFile [-c sigtype] filename (generates if nonexistent, then prints)\n" +
|
System.err.println("Usage: PrivateKeyFile filename (generates if nonexistent, then prints)\n" +
|
||||||
" PrivateKeyFile -a example.i2p filename (generate addressbook authentication string)\n" +
|
" \ncertificate options:\n" +
|
||||||
" PrivateKeyFile -h filename (generates if nonexistent, adds hashcash cert)\n" +
|
" -h (generates if nonexistent, adds hashcash cert)\n" +
|
||||||
" PrivateKeyFile -h -e effort filename (specify HashCash effort instead of default " + HASH_EFFORT + ")\n" +
|
" -n (changes to null cert)\n" +
|
||||||
" PrivateKeyFile -n filename (changes to null cert)\n" +
|
" -s signwithdestfile (generates if nonexistent, adds cert signed by 2nd dest)\n" +
|
||||||
" PrivateKeyFile -s filename signwithdestfile (generates if nonexistent, adds cert signed by 2nd dest)\n" +
|
" -u (changes to unknown cert)\n" +
|
||||||
" PrivateKeyFile -t sigtype filename (changes to KeyCertificate of the given sig type)\n" +
|
" -x (changes to hidden cert)\n" +
|
||||||
" PrivateKeyFile -u filename (changes to unknown cert)\n" +
|
" \nother options:\n" +
|
||||||
" PrivateKeyFile -x filename (changes to hidden cert)\n");
|
" -a example.i2p (generate addressbook authentication string)\n" +
|
||||||
|
" -d days (specify expiration in days of offline sig, default 365)\n" +
|
||||||
|
" -c sigtype (specify sig type of destination)\n" +
|
||||||
|
" -e effort (specify HashCash effort instead of default " + HASH_EFFORT + ")\n" +
|
||||||
|
" -o offlinedestfile (generate the online key file using the offline key file specified)\n" +
|
||||||
|
" -r sigtype (specify sig type of transient key, default Ed25519)\n" +
|
||||||
|
" -t sigtype (changes to KeyCertificate of the given sig type)\n" +
|
||||||
|
"");
|
||||||
}
|
}
|
||||||
|
|
||||||
public PrivateKeyFile(String file) {
|
public PrivateKeyFile(String file) {
|
||||||
@ -358,6 +447,16 @@ public class PrivateKeyFile {
|
|||||||
this.dest = new VerifiedDestination(s.getMyDestination());
|
this.dest = new VerifiedDestination(s.getMyDestination());
|
||||||
this.privKey = s.getDecryptionKey();
|
this.privKey = s.getDecryptionKey();
|
||||||
this.signingPrivKey = s.getPrivateKey();
|
this.signingPrivKey = s.getPrivateKey();
|
||||||
|
if (s.isOffline()) {
|
||||||
|
_offlineExpiration = s.getOfflineExpiration();
|
||||||
|
_transientSigningPubKey = s.getTransientSigningPublicKey();
|
||||||
|
_offlineSignature = s.getOfflineSignature();
|
||||||
|
_transientSigningPrivKey = signingPrivKey;
|
||||||
|
// set dummy SPK
|
||||||
|
SigType type = dest.getSigningPublicKey().getType();
|
||||||
|
byte[] dbytes = new byte[type.getPrivkeyLen()];
|
||||||
|
signingPrivKey = new SigningPrivateKey(type, dbytes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.dest;
|
return this.dest;
|
||||||
@ -480,7 +579,7 @@ public class PrivateKeyFile {
|
|||||||
System.arraycopy(this.dest.getPublicKey().getData(), 0, data, 0, PublicKey.KEYSIZE_BYTES);
|
System.arraycopy(this.dest.getPublicKey().getData(), 0, data, 0, PublicKey.KEYSIZE_BYTES);
|
||||||
System.arraycopy(this.dest.getSigningPublicKey().getData(), 0, data, PublicKey.KEYSIZE_BYTES, SigningPublicKey.KEYSIZE_BYTES);
|
System.arraycopy(this.dest.getSigningPublicKey().getData(), 0, data, PublicKey.KEYSIZE_BYTES, SigningPublicKey.KEYSIZE_BYTES);
|
||||||
byte[] payload = new byte[Hash.HASH_LENGTH + Signature.SIGNATURE_BYTES];
|
byte[] payload = new byte[Hash.HASH_LENGTH + Signature.SIGNATURE_BYTES];
|
||||||
Signature sign = DSAEngine.getInstance().sign(new ByteArrayInputStream(data), spk2);
|
Signature sign = DSAEngine.getInstance().sign(data, spk2);
|
||||||
if (sign == null)
|
if (sign == null)
|
||||||
return null;
|
return null;
|
||||||
byte[] sig = sign.getData();
|
byte[] sig = sign.getData();
|
||||||
@ -519,6 +618,91 @@ public class PrivateKeyFile {
|
|||||||
return this.signingPrivKey;
|
return this.signingPrivKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//// offline methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constant time
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
private static boolean isOffline(SigningPrivateKey spk) {
|
||||||
|
byte b = 0;
|
||||||
|
byte[] data = spk.getData();
|
||||||
|
for (int i = 0; i < data.length; i++) {
|
||||||
|
b |= data[i];
|
||||||
|
}
|
||||||
|
return b == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does this session have offline and transient keys?
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
public boolean isOffline() {
|
||||||
|
return _offlineSignature != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Side effect - zeroes out the current signing private key
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
public void setOfflineData(long expires, SigningPublicKey transientPub, Signature sig, SigningPrivateKey transientPriv) {
|
||||||
|
if (!isOffline(signingPrivKey)) {
|
||||||
|
SigType type = getSigningPrivKey().getType();
|
||||||
|
byte[] dbytes = new byte[type.getPrivkeyLen()];
|
||||||
|
signingPrivKey = new SigningPrivateKey(type, dbytes);
|
||||||
|
}
|
||||||
|
_offlineExpiration = expires;
|
||||||
|
_transientSigningPubKey = transientPub;
|
||||||
|
_offlineSignature = sig;
|
||||||
|
_transientSigningPrivKey = transientPriv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Java time (ms) or 0 if not initialized or does not have offline keys
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
public long getOfflineExpiration() {
|
||||||
|
return _offlineExpiration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
public Signature getOfflineSignature() {
|
||||||
|
return _offlineSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return null on error or if not initialized or does not have offline keys
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
public SigningPublicKey getTransientSigningPubKey() {
|
||||||
|
try {
|
||||||
|
// call this to force initialization
|
||||||
|
getDestination();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return _transientSigningPubKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return null on error or if not initialized or does not have offline keys
|
||||||
|
* @since 0.9.38
|
||||||
|
*/
|
||||||
|
public SigningPrivateKey getTransientSigningPrivKey() {
|
||||||
|
try {
|
||||||
|
// call this to force initialization
|
||||||
|
getDestination();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return _transientSigningPrivKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
//// end offline methods
|
||||||
|
|
||||||
|
|
||||||
public I2PSession open() throws I2PSessionException, IOException {
|
public I2PSession open() throws I2PSessionException, IOException {
|
||||||
return this.open(new Properties());
|
return this.open(new Properties());
|
||||||
}
|
}
|
||||||
@ -546,6 +730,13 @@ public class PrivateKeyFile {
|
|||||||
this.dest.writeBytes(out);
|
this.dest.writeBytes(out);
|
||||||
this.privKey.writeBytes(out);
|
this.privKey.writeBytes(out);
|
||||||
this.signingPrivKey.writeBytes(out);
|
this.signingPrivKey.writeBytes(out);
|
||||||
|
if (isOffline()) {
|
||||||
|
DataHelper.writeLong(out, 4, _offlineExpiration / 1000);
|
||||||
|
DataHelper.writeLong(out, 2, _transientSigningPubKey.getType().getCode());
|
||||||
|
_transientSigningPubKey.writeBytes(out);
|
||||||
|
_offlineSignature.writeBytes(out);
|
||||||
|
_transientSigningPrivKey.writeBytes(out);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (out != null) {
|
if (out != null) {
|
||||||
try { out.close(); } catch (IOException ioe) {}
|
try { out.close(); } catch (IOException ioe) {}
|
||||||
@ -582,8 +773,19 @@ public class PrivateKeyFile {
|
|||||||
s.append("\nPrivate Key: ");
|
s.append("\nPrivate Key: ");
|
||||||
s.append(this.privKey);
|
s.append(this.privKey);
|
||||||
s.append("\nSigining Private Key: ");
|
s.append("\nSigining Private Key: ");
|
||||||
s.append(this.signingPrivKey);
|
if (isOffline()) {
|
||||||
s.append("\n");
|
s.append("offline\nOffline Signature Expires: ");
|
||||||
|
s.append(new Date(getOfflineExpiration()));
|
||||||
|
s.append("\nTransient Signing Public Key: ");
|
||||||
|
s.append(_transientSigningPubKey);
|
||||||
|
s.append("\nOffline Signature: ");
|
||||||
|
s.append(_offlineSignature);
|
||||||
|
s.append("\nTransient Signing Private Key: ");
|
||||||
|
s.append(_transientSigningPrivKey);
|
||||||
|
} else {
|
||||||
|
s.append(this.signingPrivKey);
|
||||||
|
s.append("\n");
|
||||||
|
}
|
||||||
return s.toString();
|
return s.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
13
history.txt
13
history.txt
@ -1,3 +1,16 @@
|
|||||||
|
2018-12-04 zzz
|
||||||
|
* Data: Add preliminary PrivateKeyFile support for LS2 offline keys (proposal #123)
|
||||||
|
* I2CP: Add preliminary support for LS2 offline keys (proposal #123)
|
||||||
|
|
||||||
|
2018-12-03 zzz
|
||||||
|
* I2CP: Consolidate all the port 7654 definitions
|
||||||
|
* NetDb: Don't send our RI in response to DSM when shutting down
|
||||||
|
* Wizard: Update text
|
||||||
|
|
||||||
|
2018-12-02 zzz
|
||||||
|
* Router: Allow LS2 DSM down a tunnel
|
||||||
|
* Transport: Add methods to force-disconnect a peer
|
||||||
|
|
||||||
2018-12-01 zzz
|
2018-12-01 zzz
|
||||||
* I2CP: Add preliminary support for LS2 (proposal #123)
|
* I2CP: Add preliminary support for LS2 (proposal #123)
|
||||||
* Router: More support for LS2 types (proposal #123)
|
* Router: More support for LS2 types (proposal #123)
|
||||||
|
@ -18,7 +18,7 @@ public class RouterVersion {
|
|||||||
/** deprecated */
|
/** deprecated */
|
||||||
public final static String ID = "Monotone";
|
public final static String ID = "Monotone";
|
||||||
public final static String VERSION = CoreVersion.VERSION;
|
public final static String VERSION = CoreVersion.VERSION;
|
||||||
public final static long BUILD = 7;
|
public final static long BUILD = 8;
|
||||||
|
|
||||||
/** for example "-test" */
|
/** for example "-test" */
|
||||||
public final static String EXTRA = "";
|
public final static String EXTRA = "";
|
||||||
|
Reference in New Issue
Block a user