diff --git a/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java b/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java index 793982aed3..88f2d84b48 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java @@ -45,7 +45,8 @@ abstract class SAMMessageSession implements SAMMessageSess { /** * Initialize a new SAM message-based session. * - * @param dest Base64-encoded destination and private keys (same format as PrivateKeyFile) + * @param dest Base64-encoded destination and private keys, + * and optional offline signature section (same format as PrivateKeyFile) * @param props Properties to setup the I2P session * @throws IOException * @throws DataFormatException @@ -58,7 +59,8 @@ abstract class SAMMessageSession implements SAMMessageSess { /** * Initialize a new SAM message-based session. * - * @param destStream Input stream containing the destination and private keys (same format as PrivateKeyFile) + * @param destStream Input stream containing the binary destination and private keys, + * and optional offline signature section (same format as PrivateKeyFile) * @param props Properties to setup the I2P session * @throws IOException * @throws DataFormatException diff --git a/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java b/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java index 193cd76e07..4724cadb7e 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java @@ -85,7 +85,8 @@ class SAMStreamSession implements SAMMessageSess { * * Caller MUST call start(). * - * @param dest Base64-encoded destination and private keys (same format as PrivateKeyFile) + * @param dest Base64-encoded destination and private keys, + * and optional offline signature section (same format as PrivateKeyFile) * @param dir Session direction ("RECEIVE", "CREATE" or "BOTH") or "__v3__" if extended by SAMv3StreamSession * @param props Properties to setup the I2P session * @param recv Object that will receive incoming data @@ -101,7 +102,10 @@ class SAMStreamSession implements SAMMessageSess { /** * Create a new SAM STREAM session. * - * @param destStream Input stream containing the destination and private keys (same format as PrivateKeyFile) + * Caller MUST call start(). + * + * @param destStream Input stream containing the binary destination and private keys, + * and optional offline signature section (same format as PrivateKeyFile) * @param dir Session direction ("RECEIVE", "CREATE" or "BOTH") or "__v3__" if extended by SAMv3StreamSession * @param props Properties to setup the I2P session * @param recv Object that will receive incoming data diff --git a/apps/sam/java/src/net/i2p/sam/SAMUtils.java b/apps/sam/java/src/net/i2p/sam/SAMUtils.java index 756ec075e2..a17ec1f000 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMUtils.java +++ b/apps/sam/java/src/net/i2p/sam/SAMUtils.java @@ -23,9 +23,12 @@ import net.i2p.client.naming.NamingService; import net.i2p.crypto.SigType; import net.i2p.data.Base64; import net.i2p.data.DataFormatException; +import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.data.PrivateKey; +import net.i2p.data.Signature; import net.i2p.data.SigningPrivateKey; +import net.i2p.data.SigningPublicKey; /** * Miscellaneous utility methods used by SAM protocol handlers. @@ -95,7 +98,10 @@ class SAMUtils { ****/ /** - * Check whether a base64-encoded {dest,privkey,signingprivkey} is valid + * Check whether a base64-encoded {dest,privkey,signingprivkey[,offlinesig]} is valid + * + * This only checks that the length is correct. It does not validate + * for pubkey/privkey match, or check the signatures. * * @param dest The base64-encoded destination and keys to be checked (same format as PrivateKeyFile) * @return true if valid @@ -106,18 +112,46 @@ class SAMUtils { return false; ByteArrayInputStream destKeyStream = new ByteArrayInputStream(b); try { - Destination d = Destination.create(destKeyStream); - new PrivateKey().readBytes(destKeyStream); - SigningPrivateKey spk = new SigningPrivateKey(d.getSigningPublicKey().getType()); - spk.readBytes(destKeyStream); - } catch (DataFormatException e) { + Destination d = Destination.create(destKeyStream); + new PrivateKey().readBytes(destKeyStream); + SigType dtype = d.getSigningPublicKey().getType(); + SigningPrivateKey spk = new SigningPrivateKey(dtype); + spk.readBytes(destKeyStream); + if (isOffline(spk)) { + // offlineExpiration + DataHelper.readLong(destKeyStream, 4); + int itype = (int) DataHelper.readLong(destKeyStream, 2); + SigType type = SigType.getByCode(itype); + if (type == null) + return false; + SigningPublicKey transientSigningPublicKey = new SigningPublicKey(type); + transientSigningPublicKey.readBytes(destKeyStream); + Signature offlineSignature = new Signature(dtype); + offlineSignature.readBytes(destKeyStream); + // replace spk + spk = new SigningPrivateKey(type); + spk.readBytes(destKeyStream); + } + } catch (DataFormatException e) { return false; - } catch (IOException e) { + } catch (IOException e) { return false; - } + } return destKeyStream.available() == 0; } + /** + * @since 0.9.39 + */ + private static boolean isOffline(SigningPrivateKey spk) { + byte[] data = spk.getData(); + for (int i = 0; i < data.length; i++) { + if (data[i] != 0) + return false; + } + return true; + } + /** * Resolved the specified hostname.