forked from I2P_Developers/i2p.i2p
I2P changes for ratchet
This commit is contained in:
@ -128,6 +128,10 @@ Public domain except as listed below:
|
|||||||
Copyright (C) 2006 The Android Open Source Project
|
Copyright (C) 2006 The Android Open Source Project
|
||||||
See licenses/LICENSE-Apache2.0.txt
|
See licenses/LICENSE-Apache2.0.txt
|
||||||
|
|
||||||
|
ML-KEM:
|
||||||
|
Copyright (c) 2000-2023 The Legion Of The Bouncy Castle Inc. (https://www.bouncycastle.org)
|
||||||
|
See licenses/LICENSE-Bouncycastle.txt
|
||||||
|
|
||||||
|
|
||||||
Installer:
|
Installer:
|
||||||
(not included in distribution packages)
|
(not included in distribution packages)
|
||||||
|
@ -604,6 +604,9 @@
|
|||||||
<%
|
<%
|
||||||
boolean has0 = editBean.hasEncType(curTunnel, 0);
|
boolean has0 = editBean.hasEncType(curTunnel, 0);
|
||||||
boolean has4 = editBean.hasEncType(curTunnel, 4);
|
boolean has4 = editBean.hasEncType(curTunnel, 4);
|
||||||
|
boolean has5 = editBean.hasEncType(curTunnel, 5);
|
||||||
|
boolean has6 = editBean.hasEncType(curTunnel, 6);
|
||||||
|
boolean has7 = editBean.hasEncType(curTunnel, 7);
|
||||||
%>
|
%>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="2" <%=ehdisabled%>>
|
<th colspan="2" <%=ehdisabled%>>
|
||||||
@ -617,7 +620,25 @@
|
|||||||
<option title="<%=intl._t("This is the default, recommended option")%>" value="4" <%=((has4 && !has0) ? " selected=\"selected\"" : edisabled)%> >
|
<option title="<%=intl._t("This is the default, recommended option")%>" value="4" <%=((has4 && !has0) ? " selected=\"selected\"" : edisabled)%> >
|
||||||
ECIES-X25519</option>
|
ECIES-X25519</option>
|
||||||
<option value="4,0" <%=((has0 && has4) ? " selected=\"selected\"" : edisabled)%> >
|
<option value="4,0" <%=((has0 && has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||||
<%=intl._t("Both encryption types")%></option>
|
ECIES-X25519 + ElGamal-2048</option>
|
||||||
|
<%
|
||||||
|
if (editBean.isAdvanced()) {
|
||||||
|
%>
|
||||||
|
<option title="<%=intl._t("experimental")%>" value="5" <%=((has5 && !has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||||
|
MLKEM512-X25519</option>
|
||||||
|
<option title="<%=intl._t("experimental")%>" value="6" <%=((has6 && !has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||||
|
MLKEM768-X25519</option>
|
||||||
|
<option title="<%=intl._t("experimental")%>" value="7" <%=((has7 && !has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||||
|
MLKEM1024-X25519</option>
|
||||||
|
<option title="<%=intl._t("experimental")%>" value="5,4" <%=((has5 && has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||||
|
MLKEM512-X25519 + ECIES-X25519</option>
|
||||||
|
<option title="<%=intl._t("experimental")%>" value="6,4" <%=((has6 && has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||||
|
MLKEM768-X25519 + ECIES-X25519</option>
|
||||||
|
<option title="<%=intl._t("experimental")%>" value="7,4" <%=((has7 && has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||||
|
MLKEM1024-X25519 + ECIES-X25519</option>
|
||||||
|
<%
|
||||||
|
} // isAdvanced()
|
||||||
|
%>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -676,6 +676,9 @@
|
|||||||
<%
|
<%
|
||||||
boolean has0 = editBean.hasEncType(curTunnel, 0);
|
boolean has0 = editBean.hasEncType(curTunnel, 0);
|
||||||
boolean has4 = editBean.hasEncType(curTunnel, 4);
|
boolean has4 = editBean.hasEncType(curTunnel, 4);
|
||||||
|
boolean has5 = editBean.hasEncType(curTunnel, 5);
|
||||||
|
boolean has6 = editBean.hasEncType(curTunnel, 6);
|
||||||
|
boolean has7 = editBean.hasEncType(curTunnel, 7);
|
||||||
String edisabled = canChangeEncType ? "" : " disabled=\"disabled\" ";
|
String edisabled = canChangeEncType ? "" : " disabled=\"disabled\" ";
|
||||||
%>
|
%>
|
||||||
<tr>
|
<tr>
|
||||||
@ -690,7 +693,25 @@
|
|||||||
<option title="<%=intl._t("This is the default, recommended option")%>" value="4" <%=((has4 && !has0) ? " selected=\"selected\"" : edisabled)%> >
|
<option title="<%=intl._t("This is the default, recommended option")%>" value="4" <%=((has4 && !has0) ? " selected=\"selected\"" : edisabled)%> >
|
||||||
ECIES-X25519</option>
|
ECIES-X25519</option>
|
||||||
<option value="4,0" <%=((has0 && has4) ? " selected=\"selected\"" : edisabled)%> >
|
<option value="4,0" <%=((has0 && has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||||
<%=intl._t("Both encryption types")%></option>
|
ECIES-X25519 + ElGamal-2048</option>
|
||||||
|
<%
|
||||||
|
if (editBean.isAdvanced()) {
|
||||||
|
%>
|
||||||
|
<option title="<%=intl._t("experimental")%>" value="5" <%=((has5 && !has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||||
|
MLKEM512-X25519</option>
|
||||||
|
<option title="<%=intl._t("experimental")%>" value="6" <%=((has6 && !has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||||
|
MLKEM768-X25519</option>
|
||||||
|
<option title="<%=intl._t("experimental")%>" value="7" <%=((has7 && !has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||||
|
MLKEM1024-X25519</option>
|
||||||
|
<option title="<%=intl._t("experimental")%>" value="5,4" <%=((has5 && has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||||
|
MLKEM512-X25519 + ECIES-X25519</option>
|
||||||
|
<option title="<%=intl._t("experimental")%>" value="6,4" <%=((has6 && has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||||
|
MLKEM768-X25519 + ECIES-X25519</option>
|
||||||
|
<option title="<%=intl._t("experimental")%>" value="7,4" <%=((has7 && has4) ? " selected=\"selected\"" : edisabled)%> >
|
||||||
|
MLKEM1024-X25519 + ECIES-X25519</option>
|
||||||
|
<%
|
||||||
|
} // isAdvanced()
|
||||||
|
%>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -887,7 +887,7 @@
|
|||||||
windowtitle="I2P Anonymous Network - Java Documentation - API Version ${api.version}">
|
windowtitle="I2P Anonymous Network - Java Documentation - API Version ${api.version}">
|
||||||
<group title="Core SDK (i2p.jar)" packages="net.i2p:net.i2p.*:net.i2p.client:net.i2p.client.*:net.i2p.internal:net.i2p.internal.*:freenet.support.CPUInformation:gnu.crypto.*:gnu.getopt:gnu.gettext:com.nettgryppa.security:net.i2p.apache.http.conn.ssl:net.i2p.apache.http.conn.util:net.i2p.apache.http.util:org.json.simple:com.southernstorm.noise.crypto.x25519:com.southernstorm.noise.crypto.chacha20:org.minidns:org.minidns.*" />
|
<group title="Core SDK (i2p.jar)" packages="net.i2p:net.i2p.*:net.i2p.client:net.i2p.client.*:net.i2p.internal:net.i2p.internal.*:freenet.support.CPUInformation:gnu.crypto.*:gnu.getopt:gnu.gettext:com.nettgryppa.security:net.i2p.apache.http.conn.ssl:net.i2p.apache.http.conn.util:net.i2p.apache.http.util:org.json.simple:com.southernstorm.noise.crypto.x25519:com.southernstorm.noise.crypto.chacha20:org.minidns:org.minidns.*" />
|
||||||
<group title="Streaming Library" packages="net.i2p.client.streaming:net.i2p.client.streaming.impl" />
|
<group title="Streaming Library" packages="net.i2p.client.streaming:net.i2p.client.streaming.impl" />
|
||||||
<group title="Router" packages="net.i2p.router:net.i2p.router.*:net.i2p.data.i2np:net.i2p.data.router:org.cybergarage:org.cybergarage.*:org.freenetproject:org.xlattice.crypto.filters:com.maxmind.*:com.southernstorm.noise.*" />
|
<group title="Router" packages="net.i2p.router:net.i2p.router.*:net.i2p.data.i2np:net.i2p.data.router:org.cybergarage:org.cybergarage.*:org.freenetproject:org.xlattice.crypto.filters:com.maxmind.*:com.southernstorm.noise.*:org.bouncycastle:org.bouncycastle.*" />
|
||||||
<group title="Router Console" packages="net.i2p.router.web:net.i2p.router.web.*:net.i2p.router.update:edu.internet2.ndt:net.i2p.router.news:com.vuze.*" />
|
<group title="Router Console" packages="net.i2p.router.web:net.i2p.router.web.*:net.i2p.router.update:edu.internet2.ndt:net.i2p.router.news:com.vuze.*" />
|
||||||
<!-- apps and bridges starting here, alphabetical please -->
|
<!-- apps and bridges starting here, alphabetical please -->
|
||||||
<group title="Addressbook Application" packages="net.i2p.addressbook:net.i2p.router.naming:net.metanotion:net.metanotion.*" />
|
<group title="Addressbook Application" packages="net.i2p.addressbook:net.i2p.router.naming:net.metanotion:net.metanotion.*" />
|
||||||
|
@ -11,7 +11,14 @@ public enum EncAlgo {
|
|||||||
EC("EC"),
|
EC("EC"),
|
||||||
|
|
||||||
/** @since 0.9.38 */
|
/** @since 0.9.38 */
|
||||||
ECIES("ECIES");
|
ECIES("ECIES"),
|
||||||
|
|
||||||
|
/** @since 0.9.67 */
|
||||||
|
ECIES_MLKEM("ECIES-MLKEM"),
|
||||||
|
|
||||||
|
/** @since 0.9.67 */
|
||||||
|
ECIES_MLKEM_INT("ECIES-MLKEM-Internal");
|
||||||
|
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
|
@ -52,14 +52,83 @@ public enum EncType {
|
|||||||
* Pubkey 32 bytes; privkey 32 bytes
|
* Pubkey 32 bytes; privkey 32 bytes
|
||||||
* @since 0.9.38
|
* @since 0.9.38
|
||||||
*/
|
*/
|
||||||
ECIES_X25519(4, 32, 32, EncAlgo.ECIES, "EC/None/NoPadding", X25519_SPEC, "0.9.38");
|
ECIES_X25519(4, 32, 32, EncAlgo.ECIES, "EC/None/NoPadding", X25519_SPEC, "0.9.38"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proposal 169.
|
||||||
|
* Pubkey 32 bytes; privkey 32 bytes
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
MLKEM512_X25519(5, 32, 32, EncAlgo.ECIES_MLKEM, "EC/None/NoPadding", X25519_SPEC, "0.9.67"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proposal 169.
|
||||||
|
* Pubkey 32 bytes; privkey 32 bytes
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
MLKEM768_X25519(6, 32, 32, EncAlgo.ECIES_MLKEM, "EC/None/NoPadding", X25519_SPEC, "0.9.67"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proposal 169.
|
||||||
|
* Pubkey 32 bytes; privkey 32 bytes
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
MLKEM1024_X25519(7, 32, 32, EncAlgo.ECIES_MLKEM, "EC/None/NoPadding", X25519_SPEC, "0.9.67"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For internal use only (Alice side)
|
||||||
|
* Proposal 169.
|
||||||
|
* Pubkey 800 bytes; privkey 1632 bytes
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
MLKEM512_X25519_INT(100005, 800, 1632, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.67"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For internal use only (Alice side)
|
||||||
|
* Proposal 169.
|
||||||
|
* Pubkey 1184 bytes; privkey 2400 bytes
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
MLKEM768_X25519_INT(100006, 1184, 2400, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.67"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For internal use only (Alice side)
|
||||||
|
* Proposal 169.
|
||||||
|
* Pubkey 1568 bytes; privkey 3168 bytes
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
MLKEM1024_X25519_INT(100007, 1568, 3168, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.67"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For internal use only (Bob side ciphertext)
|
||||||
|
* Proposal 169.
|
||||||
|
* Pubkey 768 bytes; privkey 0
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
MLKEM512_X25519_CT(100008, 768, 0, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.67"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For internal use only (Bob side ciphertext)
|
||||||
|
* Proposal 169.
|
||||||
|
* Pubkey 1088 bytes; privkey 0
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
MLKEM768_X25519_CT(100009, 1088, 0, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.67"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For internal use only (Bob side ciphertext)
|
||||||
|
* Proposal 169.
|
||||||
|
* Pubkey 1568 bytes; privkey 0
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
MLKEM1024_X25519_CT(100010, 1568, 0, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.67");
|
||||||
|
|
||||||
|
|
||||||
private final int code, pubkeyLen, privkeyLen;
|
private final int code, pubkeyLen, privkeyLen;
|
||||||
private final EncAlgo base;
|
private final EncAlgo base;
|
||||||
private final String algoName, since;
|
private final String algoName, since;
|
||||||
private final AlgorithmParameterSpec params;
|
private final AlgorithmParameterSpec params;
|
||||||
private final boolean isAvail;
|
private final boolean isAvail, isPQ;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -68,7 +137,7 @@ public enum EncType {
|
|||||||
*/
|
*/
|
||||||
EncType(int cod, int pubLen, int privLen, EncAlgo baseAlgo,
|
EncType(int cod, int pubLen, int privLen, EncAlgo baseAlgo,
|
||||||
String transformation, AlgorithmParameterSpec pSpec, String supportedSince) {
|
String transformation, AlgorithmParameterSpec pSpec, String supportedSince) {
|
||||||
if (pubLen > 256)
|
if (pubLen > 256 && baseAlgo != EncAlgo.ECIES_MLKEM_INT)
|
||||||
throw new IllegalArgumentException("fixup PublicKey for longer keys");
|
throw new IllegalArgumentException("fixup PublicKey for longer keys");
|
||||||
code = cod;
|
code = cod;
|
||||||
pubkeyLen = pubLen;
|
pubkeyLen = pubLen;
|
||||||
@ -78,6 +147,7 @@ public enum EncType {
|
|||||||
params = pSpec;
|
params = pSpec;
|
||||||
since = supportedSince;
|
since = supportedSince;
|
||||||
isAvail = x_isAvailable();
|
isAvail = x_isAvailable();
|
||||||
|
isPQ = base == EncAlgo.ECIES_MLKEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** the unique identifier for this type */
|
/** the unique identifier for this type */
|
||||||
@ -120,11 +190,16 @@ public enum EncType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean x_isAvailable() {
|
private boolean x_isAvailable() {
|
||||||
if (ELGAMAL_2048 == this)
|
switch (base) {
|
||||||
|
case ELGAMAL:
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// EC types are placeholders for now
|
// EC types are placeholders for now
|
||||||
if (base == EncAlgo.EC)
|
case EC:
|
||||||
|
// internal types
|
||||||
|
case ECIES_MLKEM_INT:
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
getParams();
|
getParams();
|
||||||
} catch (InvalidParameterSpecException e) {
|
} catch (InvalidParameterSpecException e) {
|
||||||
@ -154,6 +229,14 @@ public enum EncType {
|
|||||||
return type.isAvailable();
|
return type.isAvailable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.9.67
|
||||||
|
* @return true if this is a PQ type
|
||||||
|
*/
|
||||||
|
public boolean isPQ() {
|
||||||
|
return isPQ;
|
||||||
|
}
|
||||||
|
|
||||||
private static final EncType[] BY_CODE;
|
private static final EncType[] BY_CODE;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
@ -196,6 +196,9 @@ public final class KeyGenerator {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ECIES_X25519:
|
case ECIES_X25519:
|
||||||
|
case MLKEM512_X25519:
|
||||||
|
case MLKEM768_X25519:
|
||||||
|
case MLKEM1024_X25519:
|
||||||
byte[] bpriv = new byte[32];
|
byte[] bpriv = new byte[32];
|
||||||
do {
|
do {
|
||||||
_context.random().nextBytes(bpriv);
|
_context.random().nextBytes(bpriv);
|
||||||
@ -238,6 +241,9 @@ public final class KeyGenerator {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ECIES_X25519:
|
case ECIES_X25519:
|
||||||
|
case MLKEM512_X25519:
|
||||||
|
case MLKEM768_X25519:
|
||||||
|
case MLKEM1024_X25519:
|
||||||
data = new byte[32];
|
data = new byte[32];
|
||||||
Curve25519.eval(data, 0, priv.getData(), null);
|
Curve25519.eval(data, 0, priv.getData(), null);
|
||||||
break;
|
break;
|
||||||
@ -342,6 +348,7 @@ public final class KeyGenerator {
|
|||||||
}
|
}
|
||||||
java.security.PublicKey pubkey = kp.getPublic();
|
java.security.PublicKey pubkey = kp.getPublic();
|
||||||
java.security.PrivateKey privkey = kp.getPrivate();
|
java.security.PrivateKey privkey = kp.getPrivate();
|
||||||
|
|
||||||
SimpleDataStructure[] keys = new SimpleDataStructure[2];
|
SimpleDataStructure[] keys = new SimpleDataStructure[2];
|
||||||
keys[0] = SigUtil.fromJavaKey(pubkey, type);
|
keys[0] = SigUtil.fromJavaKey(pubkey, type);
|
||||||
keys[1] = SigUtil.fromJavaKey(privkey, type);
|
keys[1] = SigUtil.fromJavaKey(privkey, type);
|
||||||
@ -476,6 +483,7 @@ public final class KeyGenerator {
|
|||||||
System.out.println(type + " private-to-public test PASSED");
|
System.out.println(type + " private-to-public test PASSED");
|
||||||
else
|
else
|
||||||
System.out.println(type + " private-to-public test FAILED");
|
System.out.println(type + " private-to-public test FAILED");
|
||||||
|
|
||||||
//System.out.println("privkey " + keys[1]);
|
//System.out.println("privkey " + keys[1]);
|
||||||
MessageDigest md = type.getDigestInstance();
|
MessageDigest md = type.getDigestInstance();
|
||||||
for (int i = 0; i < runs; i++) {
|
for (int i = 0; i < runs; i++) {
|
||||||
|
21
licenses/LICENSE-Bouncycastle.txt
Normal file
21
licenses/LICENSE-Bouncycastle.txt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* The Bouncy Castle License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2000-2023 The Legion Of The Bouncy Castle Inc. (https://www.bouncycastle.org)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||||
|
* and associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||||
|
* portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
* DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
@ -39,12 +39,15 @@ public class HandshakeState implements Destroyable, Cloneable {
|
|||||||
private final boolean isInitiator;
|
private final boolean isInitiator;
|
||||||
private DHState localKeyPair;
|
private DHState localKeyPair;
|
||||||
private DHState localEphemeral;
|
private DHState localEphemeral;
|
||||||
|
private DHState localHybrid;
|
||||||
private DHState remotePublicKey;
|
private DHState remotePublicKey;
|
||||||
private DHState remoteEphemeral;
|
private DHState remoteEphemeral;
|
||||||
|
private DHState remoteHybrid;
|
||||||
private int action;
|
private int action;
|
||||||
private final int requirements;
|
private final int requirements;
|
||||||
private int patternIndex;
|
private int patternIndex;
|
||||||
private boolean wasCloned;
|
private boolean wasCloned;
|
||||||
|
private boolean isDestroyed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enumerated value that indicates that the handshake object
|
* Enumerated value that indicates that the handshake object
|
||||||
@ -139,6 +142,14 @@ public class HandshakeState implements Destroyable, Cloneable {
|
|||||||
public static final String protocolName3 = "Noise_N_25519_ChaChaPoly_SHA256";
|
public static final String protocolName3 = "Noise_N_25519_ChaChaPoly_SHA256";
|
||||||
/** SSU2 */
|
/** SSU2 */
|
||||||
public static final String protocolName4 = "Noise_XKchaobfse+hs1+hs2+hs3_25519_ChaChaPoly_SHA256";
|
public static final String protocolName4 = "Noise_XKchaobfse+hs1+hs2+hs3_25519_ChaChaPoly_SHA256";
|
||||||
|
/**
|
||||||
|
* Hybrid Ratchet
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
public static final String protocolName5 = "Noise_IKhfselg2_25519+MLKEM512_ChaChaPoly_SHA256";
|
||||||
|
public static final String protocolName6 = "Noise_IKhfselg2_25519+MLKEM768_ChaChaPoly_SHA256";
|
||||||
|
public static final String protocolName7 = "Noise_IKhfselg2_25519+MLKEM1024_ChaChaPoly_SHA256";
|
||||||
|
|
||||||
private static final String prefix;
|
private static final String prefix;
|
||||||
private final String patternId;
|
private final String patternId;
|
||||||
/** NTCP2 */
|
/** NTCP2 */
|
||||||
@ -151,13 +162,24 @@ public class HandshakeState implements Destroyable, Cloneable {
|
|||||||
public static final String PATTERN_ID_N_NO_RESPONSE = "N!";
|
public static final String PATTERN_ID_N_NO_RESPONSE = "N!";
|
||||||
/** SSU2 */
|
/** SSU2 */
|
||||||
public static final String PATTERN_ID_XK_SSU2 = "XK-SSU2";
|
public static final String PATTERN_ID_XK_SSU2 = "XK-SSU2";
|
||||||
private static String dh;
|
/** Hybrid Base */
|
||||||
|
private static final String PATTERN_ID_IKHFS = "IKhfs";
|
||||||
|
/**
|
||||||
|
* Hybrid Ratchet
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
public static final String PATTERN_ID_IKHFS_512 = "IKhfs512";
|
||||||
|
public static final String PATTERN_ID_IKHFS_768 = "IKhfs768";
|
||||||
|
public static final String PATTERN_ID_IKHFS_1024 = "IKhfs1024";
|
||||||
|
|
||||||
|
private static final String dh;
|
||||||
private static final String cipher;
|
private static final String cipher;
|
||||||
private static final String hash;
|
private static final String hash;
|
||||||
private final short[] pattern;
|
private final short[] pattern;
|
||||||
private static final short[] PATTERN_XK;
|
private static final short[] PATTERN_XK;
|
||||||
private static final short[] PATTERN_IK;
|
private static final short[] PATTERN_IK;
|
||||||
private static final short[] PATTERN_N;
|
private static final short[] PATTERN_N;
|
||||||
|
private static final short[] PATTERN_IKHFS;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// Parse the protocol name into its components.
|
// Parse the protocol name into its components.
|
||||||
@ -200,11 +222,28 @@ public class HandshakeState implements Destroyable, Cloneable {
|
|||||||
id = components[1].substring(0, 2);
|
id = components[1].substring(0, 2);
|
||||||
if (!PATTERN_ID_XK.equals(id))
|
if (!PATTERN_ID_XK.equals(id))
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
|
// IK Hybrid
|
||||||
|
components = protocolName5.split("_");
|
||||||
|
id = components[1].substring(0, 5);
|
||||||
|
if (!PATTERN_ID_IKHFS.equals(id))
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
PATTERN_IKHFS = Pattern.lookup(id);
|
||||||
|
if (PATTERN_IKHFS == null)
|
||||||
|
throw new IllegalArgumentException("Handshake pattern is not recognized");
|
||||||
|
components = protocolName6.split("_");
|
||||||
|
id = components[1].substring(0, 5);
|
||||||
|
if (!PATTERN_ID_IKHFS.equals(id))
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
components = protocolName7.split("_");
|
||||||
|
id = components[1].substring(0, 5);
|
||||||
|
if (!PATTERN_ID_IKHFS.equals(id))
|
||||||
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new Noise handshake.
|
* Creates a new Noise handshake.
|
||||||
* Noise protocol name is hardcoded.
|
* Noise protocol name is hardcoded.
|
||||||
|
* Not for PQ Alice side.
|
||||||
*
|
*
|
||||||
* @param patternId XK, IK, or N
|
* @param patternId XK, IK, or N
|
||||||
* @param role The role, HandshakeState.INITIATOR or HandshakeState.RESPONDER.
|
* @param role The role, HandshakeState.INITIATOR or HandshakeState.RESPONDER.
|
||||||
@ -217,6 +256,26 @@ public class HandshakeState implements Destroyable, Cloneable {
|
|||||||
* that is specified in the protocolName is not supported.
|
* that is specified in the protocolName is not supported.
|
||||||
*/
|
*/
|
||||||
public HandshakeState(String patternId, int role, KeyFactory xdh) throws NoSuchAlgorithmException
|
public HandshakeState(String patternId, int role, KeyFactory xdh) throws NoSuchAlgorithmException
|
||||||
|
{
|
||||||
|
this(patternId, role, xdh, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Noise handshake.
|
||||||
|
* Noise protocol name is hardcoded.
|
||||||
|
*
|
||||||
|
* @param patternId XK, IK, or N
|
||||||
|
* @param role The role, HandshakeState.INITIATOR or HandshakeState.RESPONDER.
|
||||||
|
* @param xdh The key pair factory for ephemeral keys
|
||||||
|
* @param hdh The key pair factory for hybrid keys, Alice side only, or null for Bob or non-hybrid
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException The protocolName is not
|
||||||
|
* formatted correctly, or the role is not recognized.
|
||||||
|
*
|
||||||
|
* @throws NoSuchAlgorithmException One of the cryptographic algorithms
|
||||||
|
* that is specified in the protocolName is not supported.
|
||||||
|
*/
|
||||||
|
public HandshakeState(String patternId, int role, KeyFactory xdh, KeyFactory hdh) throws NoSuchAlgorithmException
|
||||||
{
|
{
|
||||||
this.patternId = patternId;
|
this.patternId = patternId;
|
||||||
if (patternId.equals(PATTERN_ID_XK))
|
if (patternId.equals(PATTERN_ID_XK))
|
||||||
@ -229,6 +288,10 @@ public class HandshakeState implements Destroyable, Cloneable {
|
|||||||
pattern = PATTERN_N;
|
pattern = PATTERN_N;
|
||||||
else if (patternId.equals(PATTERN_ID_XK_SSU2))
|
else if (patternId.equals(PATTERN_ID_XK_SSU2))
|
||||||
pattern = PATTERN_XK;
|
pattern = PATTERN_XK;
|
||||||
|
else if (patternId.equals(PATTERN_ID_IKHFS_512) ||
|
||||||
|
patternId.equals(PATTERN_ID_IKHFS_768) ||
|
||||||
|
patternId.equals(PATTERN_ID_IKHFS_1024))
|
||||||
|
pattern = PATTERN_IKHFS;
|
||||||
else
|
else
|
||||||
throw new IllegalArgumentException("Handshake pattern is not recognized");
|
throw new IllegalArgumentException("Handshake pattern is not recognized");
|
||||||
short flags = pattern[0];
|
short flags = pattern[0];
|
||||||
@ -256,10 +319,18 @@ public class HandshakeState implements Destroyable, Cloneable {
|
|||||||
localKeyPair = new Curve25519DHState(xdh);
|
localKeyPair = new Curve25519DHState(xdh);
|
||||||
if ((flags & Pattern.FLAG_LOCAL_EPHEMERAL) != 0)
|
if ((flags & Pattern.FLAG_LOCAL_EPHEMERAL) != 0)
|
||||||
localEphemeral = new Curve25519DHState(xdh);
|
localEphemeral = new Curve25519DHState(xdh);
|
||||||
|
if ((flags & Pattern.FLAG_LOCAL_HYBRID) != 0) {
|
||||||
|
if (isInitiator && hdh == null)
|
||||||
|
throw new IllegalArgumentException("Hybrid patterns require hybrid key generator");
|
||||||
|
localHybrid = isInitiator ? new MLKEMDHState(hdh, patternId) : new MLKEMDHState(false, patternId);
|
||||||
|
}
|
||||||
if ((flags & Pattern.FLAG_REMOTE_STATIC) != 0)
|
if ((flags & Pattern.FLAG_REMOTE_STATIC) != 0)
|
||||||
remotePublicKey = new Curve25519DHState(xdh);
|
remotePublicKey = new Curve25519DHState(xdh);
|
||||||
if ((flags & Pattern.FLAG_REMOTE_EPHEMERAL) != 0)
|
if ((flags & Pattern.FLAG_REMOTE_EPHEMERAL) != 0)
|
||||||
remoteEphemeral = new Curve25519DHState(xdh);
|
remoteEphemeral = new Curve25519DHState(xdh);
|
||||||
|
if ((flags & Pattern.FLAG_REMOTE_HYBRID) != 0) {
|
||||||
|
remoteHybrid = new MLKEMDHState(!isInitiator, patternId);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,6 +365,23 @@ public class HandshakeState implements Destroyable, Cloneable {
|
|||||||
remotePublicKey = o.remotePublicKey.clone();
|
remotePublicKey = o.remotePublicKey.clone();
|
||||||
if (o.remoteEphemeral != null)
|
if (o.remoteEphemeral != null)
|
||||||
remoteEphemeral = o.remoteEphemeral.clone();
|
remoteEphemeral = o.remoteEphemeral.clone();
|
||||||
|
if (o.localHybrid != null) {
|
||||||
|
if (isInitiator) {
|
||||||
|
// always save Alice's local keys
|
||||||
|
localHybrid = o.localHybrid.clone();
|
||||||
|
} else {
|
||||||
|
if (o.wasCloned) {
|
||||||
|
// new keys after first time for Bob
|
||||||
|
localHybrid = o.localHybrid.clone();
|
||||||
|
} else {
|
||||||
|
// first time for Bob, use the eph. keys previously generated
|
||||||
|
localHybrid = o.localHybrid;
|
||||||
|
o.wasCloned = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (o.remoteHybrid != null)
|
||||||
|
remoteHybrid = o.remoteHybrid.clone();
|
||||||
action = o.action;
|
action = o.action;
|
||||||
if (action == SPLIT || action == COMPLETE)
|
if (action == SPLIT || action == COMPLETE)
|
||||||
throw new CloneNotSupportedException("clone after NSR");
|
throw new CloneNotSupportedException("clone after NSR");
|
||||||
@ -419,6 +507,32 @@ public class HandshakeState implements Destroyable, Cloneable {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the keypair object for the local hybrid key.
|
||||||
|
*
|
||||||
|
* I2P
|
||||||
|
*
|
||||||
|
* @return The keypair, or null if a local hybrid key is not required or has not been generated.
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
public DHState getLocalHybridKeyPair()
|
||||||
|
{
|
||||||
|
return localHybrid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the keypair object for the remote hybrid key.
|
||||||
|
*
|
||||||
|
* I2P
|
||||||
|
*
|
||||||
|
* @return The keypair, or null if a remote hybrid key is not required or has not been generated.
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
public DHState getRemoteHybridKeyPair()
|
||||||
|
{
|
||||||
|
return remoteHybrid;
|
||||||
|
}
|
||||||
|
|
||||||
// Empty value for when the prologue is not supplied.
|
// Empty value for when the prologue is not supplied.
|
||||||
private static final byte[] emptyPrologue = new byte [0];
|
private static final byte[] emptyPrologue = new byte [0];
|
||||||
|
|
||||||
@ -472,17 +586,11 @@ public class HandshakeState implements Destroyable, Cloneable {
|
|||||||
if (isInitiator) {
|
if (isInitiator) {
|
||||||
if ((requirements & LOCAL_PREMSG) != 0)
|
if ((requirements & LOCAL_PREMSG) != 0)
|
||||||
symmetric.mixPublicKey(localKeyPair);
|
symmetric.mixPublicKey(localKeyPair);
|
||||||
if ((requirements & FALLBACK_PREMSG) != 0) {
|
|
||||||
symmetric.mixPublicKey(remoteEphemeral);
|
|
||||||
}
|
|
||||||
if ((requirements & REMOTE_PREMSG) != 0)
|
if ((requirements & REMOTE_PREMSG) != 0)
|
||||||
symmetric.mixPublicKey(remotePublicKey);
|
symmetric.mixPublicKey(remotePublicKey);
|
||||||
} else {
|
} else {
|
||||||
if ((requirements & REMOTE_PREMSG) != 0)
|
if ((requirements & REMOTE_PREMSG) != 0)
|
||||||
symmetric.mixPublicKey(remotePublicKey);
|
symmetric.mixPublicKey(remotePublicKey);
|
||||||
if ((requirements & FALLBACK_PREMSG) != 0) {
|
|
||||||
symmetric.mixPublicKey(localEphemeral);
|
|
||||||
}
|
|
||||||
if ((requirements & LOCAL_PREMSG) != 0)
|
if ((requirements & LOCAL_PREMSG) != 0)
|
||||||
symmetric.mixPublicKey(localKeyPair);
|
symmetric.mixPublicKey(localKeyPair);
|
||||||
}
|
}
|
||||||
@ -675,6 +783,54 @@ public class HandshakeState implements Destroyable, Cloneable {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Pattern.F:
|
||||||
|
{
|
||||||
|
// Generate a local hybrid keypair and add the public
|
||||||
|
// key to the message. If we are running fixed vector tests,
|
||||||
|
// then a fixed hybrid key may have already been provided.
|
||||||
|
if (localHybrid == null)
|
||||||
|
throw new IllegalStateException("Pattern definition error");
|
||||||
|
byte[] shared = null;
|
||||||
|
if (isInitiator) {
|
||||||
|
// Only Alice generates a keypair
|
||||||
|
localHybrid.generateKeyPair();
|
||||||
|
} else {
|
||||||
|
// Only Bob. We have to do the FF part here,
|
||||||
|
// so we split up mixDH()
|
||||||
|
// and do the localHybrid.calculate() first
|
||||||
|
// and the mixKey() after.
|
||||||
|
// mixDH(localHybrid, remoteHybrid)
|
||||||
|
// First part
|
||||||
|
len = localHybrid.getSharedKeyLength();
|
||||||
|
shared = new byte [len];
|
||||||
|
// this creates the ciphertext and puts it in localHybrid.publicKey
|
||||||
|
// IllegalArgumentException will be thrown here on bad remote key
|
||||||
|
localHybrid.calculate(shared, 0, remoteHybrid);
|
||||||
|
}
|
||||||
|
len = localHybrid.getPublicKeyLength();
|
||||||
|
macLen = symmetric.getMACLength();
|
||||||
|
if (space < (len + macLen))
|
||||||
|
throw new ShortBufferException();
|
||||||
|
localHybrid.getPublicKey(message, messagePosn);
|
||||||
|
messagePosn += symmetric.encryptAndHash(message, messagePosn, message, messagePosn, len);
|
||||||
|
if (!isInitiator) {
|
||||||
|
// Second part
|
||||||
|
// We do the rest of the FF part here while we have the shared key
|
||||||
|
symmetric.mixKey(shared, 0, shared.length);
|
||||||
|
Noise.destroy(shared);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case Pattern.FF:
|
||||||
|
{
|
||||||
|
// DH operation with initiator and responder hybrid keys.
|
||||||
|
// We are Bob.
|
||||||
|
// This is a NOOP, we did the mixDH() in Pattern.F above.
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
// Unknown token code. Abort.
|
// Unknown token code. Abort.
|
||||||
@ -858,6 +1014,35 @@ public class HandshakeState implements Destroyable, Cloneable {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Pattern.F:
|
||||||
|
{
|
||||||
|
// Decrypt and read the remote hybrid ephemeral key.
|
||||||
|
if (remoteHybrid == null)
|
||||||
|
throw new IllegalStateException("Pattern definition error");
|
||||||
|
len = remoteHybrid.getPublicKeyLength();
|
||||||
|
macLen = symmetric.getMACLength();
|
||||||
|
if (space < (len + macLen))
|
||||||
|
throw new ShortBufferException();
|
||||||
|
byte[] temp = new byte [len];
|
||||||
|
try {
|
||||||
|
if (symmetric.decryptAndHash(message, messageOffset, temp, 0, len + macLen) != len)
|
||||||
|
throw new ShortBufferException();
|
||||||
|
remoteHybrid.setPublicKey(temp, 0);
|
||||||
|
} finally {
|
||||||
|
Noise.destroy(temp);
|
||||||
|
}
|
||||||
|
messageOffset += len + macLen;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Pattern.FF:
|
||||||
|
{
|
||||||
|
// DH operation with initiator and responder hybrid keys.
|
||||||
|
// We are Alice.
|
||||||
|
mixDH(localHybrid, remoteHybrid);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
// Unknown token code. Abort.
|
// Unknown token code. Abort.
|
||||||
@ -953,17 +1138,22 @@ public class HandshakeState implements Destroyable, Cloneable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroy() {
|
public synchronized void destroy() {
|
||||||
|
isDestroyed = true;
|
||||||
if (symmetric != null)
|
if (symmetric != null)
|
||||||
symmetric.destroy();
|
symmetric.destroy();
|
||||||
if (localKeyPair != null)
|
if (localKeyPair != null)
|
||||||
localKeyPair.destroy();
|
localKeyPair.destroy();
|
||||||
if (localEphemeral != null)
|
if (localEphemeral != null)
|
||||||
localEphemeral.destroy();
|
localEphemeral.destroy();
|
||||||
|
if (localHybrid != null)
|
||||||
|
localHybrid.destroy();
|
||||||
if (remotePublicKey != null)
|
if (remotePublicKey != null)
|
||||||
remotePublicKey.destroy();
|
remotePublicKey.destroy();
|
||||||
if (remoteEphemeral != null)
|
if (remoteEphemeral != null)
|
||||||
remoteEphemeral.destroy();
|
remoteEphemeral.destroy();
|
||||||
|
if (remoteHybrid != null)
|
||||||
|
remoteHybrid.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -992,11 +1182,6 @@ public class HandshakeState implements Destroyable, Cloneable {
|
|||||||
requirements |= REMOTE_REQUIRED;
|
requirements |= REMOTE_REQUIRED;
|
||||||
requirements |= REMOTE_PREMSG;
|
requirements |= REMOTE_PREMSG;
|
||||||
}
|
}
|
||||||
if ((flags & (Pattern.FLAG_REMOTE_EPHEM_REQ |
|
|
||||||
Pattern.FLAG_LOCAL_EPHEM_REQ)) != 0) {
|
|
||||||
if (isFallback)
|
|
||||||
requirements |= FALLBACK_PREMSG;
|
|
||||||
}
|
|
||||||
return requirements;
|
return requirements;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1022,6 +1207,8 @@ public class HandshakeState implements Destroyable, Cloneable {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public synchronized HandshakeState clone() throws CloneNotSupportedException {
|
public synchronized HandshakeState clone() throws CloneNotSupportedException {
|
||||||
|
if (isDestroyed)
|
||||||
|
throw new IllegalStateException("destroyed");
|
||||||
return new HandshakeState(this);
|
return new HandshakeState(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1085,6 +1272,34 @@ public class HandshakeState implements Destroyable, Cloneable {
|
|||||||
}
|
}
|
||||||
buf.append('\n');
|
buf.append('\n');
|
||||||
|
|
||||||
|
dh = localHybrid;
|
||||||
|
if (dh != null) {
|
||||||
|
buf.append("Local hybrid public key (e1/ekem1) : ");
|
||||||
|
if (dh != null && dh.hasPublicKey()) {
|
||||||
|
tmp = new byte[dh.getPublicKeyLength()];
|
||||||
|
dh.getPublicKey(tmp, 0);
|
||||||
|
buf.append(tmp.length).append(" bytes ");
|
||||||
|
buf.append(net.i2p.data.Base64.encode(tmp));
|
||||||
|
} else {
|
||||||
|
buf.append("null");
|
||||||
|
}
|
||||||
|
buf.append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
dh = remoteHybrid;
|
||||||
|
if (dh != null) {
|
||||||
|
buf.append("Remote hybrid public key (e1/ekem1) : ");
|
||||||
|
if (dh != null && dh.hasPublicKey()) {
|
||||||
|
tmp = new byte[dh.getPublicKeyLength()];
|
||||||
|
dh.getPublicKey(tmp, 0);
|
||||||
|
buf.append(tmp.length).append(" bytes ");
|
||||||
|
buf.append(net.i2p.data.Base64.encode(tmp));
|
||||||
|
} else {
|
||||||
|
buf.append("null");
|
||||||
|
}
|
||||||
|
buf.append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,275 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
* copy of this software and associated documentation files (the "Software"),
|
||||||
|
* to deal in the Software without restriction, including without limitation
|
||||||
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
* and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included
|
||||||
|
* in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
* DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.southernstorm.noise.protocol;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import net.i2p.crypto.KeyFactory;
|
||||||
|
import net.i2p.crypto.KeyPair;
|
||||||
|
import net.i2p.crypto.EncType;
|
||||||
|
import net.i2p.router.crypto.pqc.MLKEM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of the MLKEM algorithm for the Noise protocol.
|
||||||
|
*
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
class MLKEMDHState implements DHState, Cloneable {
|
||||||
|
|
||||||
|
private final EncType type;
|
||||||
|
private final byte[] publicKey;
|
||||||
|
private final byte[] privateKey;
|
||||||
|
private int mode;
|
||||||
|
private final KeyFactory _hdh;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bob local/remote or Alice remote side, do not call generateKeyPair()
|
||||||
|
* @param isAlice true for Bob remote side, false for Bob local side and Alice remote side
|
||||||
|
*/
|
||||||
|
public MLKEMDHState(boolean isAlice, String patternId)
|
||||||
|
{
|
||||||
|
this(isAlice, null, patternId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alice local side
|
||||||
|
*/
|
||||||
|
public MLKEMDHState(KeyFactory hdh, String patternId)
|
||||||
|
{
|
||||||
|
this(true, hdh, patternId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal
|
||||||
|
*/
|
||||||
|
private MLKEMDHState(boolean isAlice, KeyFactory hdh, String patternId)
|
||||||
|
{
|
||||||
|
if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_512)) {
|
||||||
|
type = isAlice ? EncType.MLKEM512_X25519_INT : EncType.MLKEM512_X25519_CT;
|
||||||
|
} else if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_768)) {
|
||||||
|
type = isAlice ? EncType.MLKEM768_X25519_INT : EncType.MLKEM768_X25519_CT;
|
||||||
|
} else if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_1024)) {
|
||||||
|
type = isAlice ? EncType.MLKEM1024_X25519_INT : EncType.MLKEM1024_X25519_CT;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Handshake pattern is not recognized");
|
||||||
|
}
|
||||||
|
publicKey = new byte [type.getPubkeyLen()];
|
||||||
|
privateKey = isAlice ? new byte [type.getPrivkeyLen()] : null;
|
||||||
|
mode = 0;
|
||||||
|
_hdh = hdh;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
clearKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDHName() {
|
||||||
|
return "MLKEM";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: Alice/Bob sizes are different
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getPublicKeyLength() {
|
||||||
|
return type.getPubkeyLen();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: Alice/Bob sizes are different
|
||||||
|
* @return 0 for Bob
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
|
public int getPrivateKeyLength() {
|
||||||
|
return type.getPrivkeyLen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSharedKeyLength() {
|
||||||
|
return 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alice local side ONLY
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void generateKeyPair() {
|
||||||
|
if (_hdh == null)
|
||||||
|
throw new IllegalStateException("Don't keygen PQ on Bob side");
|
||||||
|
KeyPair kp = _hdh.getKeys();
|
||||||
|
System.arraycopy(kp.getPrivate().getData(), 0, privateKey, 0, type.getPrivkeyLen());
|
||||||
|
System.arraycopy(kp.getPublic().getData(), 0, publicKey, 0, type.getPubkeyLen());
|
||||||
|
mode = 0x03;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getPublicKey(byte[] key, int offset) {
|
||||||
|
System.arraycopy(publicKey, 0, key, offset, type.getPubkeyLen());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPublicKey(byte[] key, int offset) {
|
||||||
|
System.arraycopy(key, offset, publicKey, 0, type.getPubkeyLen());
|
||||||
|
if (privateKey != null)
|
||||||
|
Arrays.fill(privateKey, (byte)0);
|
||||||
|
mode = 0x01;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
|
public void getPrivateKey(byte[] key, int offset) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
|
public void setPrivateKey(byte[] key, int offset) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
|
public void setKeys(byte[] privkey, int privoffset, byte[] pubkey, int puboffset) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setToNullPublicKey() {
|
||||||
|
Arrays.fill(publicKey, (byte)0);
|
||||||
|
if (privateKey != null)
|
||||||
|
Arrays.fill(privateKey, (byte)0);
|
||||||
|
mode = 0x01;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearKey() {
|
||||||
|
Noise.destroy(publicKey);
|
||||||
|
if (privateKey != null)
|
||||||
|
Noise.destroy(privateKey);
|
||||||
|
mode = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPublicKey() {
|
||||||
|
return (mode & 0x01) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPrivateKey() {
|
||||||
|
return (mode & 0x02) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isNullPublicKey() {
|
||||||
|
if ((mode & 0x01) == 0)
|
||||||
|
return false;
|
||||||
|
int temp = 0;
|
||||||
|
for (int index = 0; index < publicKey.length; ++index)
|
||||||
|
temp |= publicKey[index];
|
||||||
|
return temp == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* I2P
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean hasEncodedPublicKey() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
|
public void getEncodedPublicKey(byte[] key, int offset) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Side effect: If we are Bob, copies the ciphertext to our public key
|
||||||
|
* so it may be written out in the message.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException on bad public key modulus
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void calculate(byte[] sharedKey, int offset, DHState publicDH) {
|
||||||
|
if (!(publicDH instanceof MLKEMDHState))
|
||||||
|
throw new IllegalArgumentException("Incompatible DH algorithms");
|
||||||
|
try {
|
||||||
|
if (hasPrivateKey()) {
|
||||||
|
// we are Alice
|
||||||
|
byte[] sk = MLKEM.decaps(type, ((MLKEMDHState)publicDH).publicKey, privateKey);
|
||||||
|
System.arraycopy(sk, 0, sharedKey, offset, sk.length);
|
||||||
|
} else if (!hasPublicKey()) {
|
||||||
|
// we are Bob
|
||||||
|
byte[][] rv = MLKEM.encaps(type, ((MLKEMDHState)publicDH).publicKey);
|
||||||
|
byte[] ct = rv[0];
|
||||||
|
byte[] sk = rv[1];
|
||||||
|
System.arraycopy(sk, 0, sharedKey, offset, sk.length);
|
||||||
|
setPublicKey(ct, 0);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
System.out.println("Calculated shared PQ key: " + net.i2p.data.Base64.encode(sharedKey, offset, 32));
|
||||||
|
} catch (GeneralSecurityException gse) {
|
||||||
|
throw new IllegalArgumentException(gse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void copyFrom(DHState other) {
|
||||||
|
if (!(other instanceof MLKEMDHState))
|
||||||
|
throw new IllegalStateException("Mismatched DH key objects");
|
||||||
|
if (other == this)
|
||||||
|
return;
|
||||||
|
MLKEMDHState dh = (MLKEMDHState)other;
|
||||||
|
if (dh.privateKey != null)
|
||||||
|
System.arraycopy(dh.privateKey, 0, privateKey, 0, type.getPrivkeyLen());
|
||||||
|
if (dh.publicKey != null)
|
||||||
|
System.arraycopy(dh.publicKey, 0, publicKey, 0, type.getPubkeyLen());
|
||||||
|
mode = dh.mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* I2P
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public MLKEMDHState clone() throws CloneNotSupportedException {
|
||||||
|
return (MLKEMDHState) super.clone();
|
||||||
|
}
|
||||||
|
}
|
@ -97,6 +97,31 @@ class Pattern {
|
|||||||
SE
|
SE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
private static final short[] noise_pattern_IKhfs = {
|
||||||
|
FLAG_LOCAL_STATIC |
|
||||||
|
FLAG_LOCAL_EPHEMERAL |
|
||||||
|
FLAG_LOCAL_HYBRID |
|
||||||
|
FLAG_REMOTE_STATIC |
|
||||||
|
FLAG_REMOTE_EPHEMERAL |
|
||||||
|
FLAG_REMOTE_HYBRID |
|
||||||
|
FLAG_REMOTE_REQUIRED,
|
||||||
|
|
||||||
|
E,
|
||||||
|
ES,
|
||||||
|
F,
|
||||||
|
S,
|
||||||
|
SS,
|
||||||
|
FLIP_DIR,
|
||||||
|
E,
|
||||||
|
EE,
|
||||||
|
F,
|
||||||
|
FF,
|
||||||
|
SE
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Look up the description information for a pattern.
|
* Look up the description information for a pattern.
|
||||||
*
|
*
|
||||||
@ -111,6 +136,8 @@ class Pattern {
|
|||||||
return noise_pattern_XK;
|
return noise_pattern_XK;
|
||||||
else if (name.equals("IK"))
|
else if (name.equals("IK"))
|
||||||
return noise_pattern_IK;
|
return noise_pattern_IK;
|
||||||
|
else if (name.equals("IKhfs"))
|
||||||
|
return noise_pattern_IKhfs;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,17 +41,26 @@ class SymmetricState implements Destroyable, Cloneable {
|
|||||||
private static final byte[] INIT_CK_IK;
|
private static final byte[] INIT_CK_IK;
|
||||||
private static final byte[] INIT_CK_N;
|
private static final byte[] INIT_CK_N;
|
||||||
private static final byte[] INIT_CK_XK_SSU2;
|
private static final byte[] INIT_CK_XK_SSU2;
|
||||||
|
private static final byte[] INIT_CK_IKHFS_512;
|
||||||
|
private static final byte[] INIT_CK_IKHFS_768;
|
||||||
|
private static final byte[] INIT_CK_IKHFS_1024;
|
||||||
// precalculated hash of the hash of the Noise name = mixHash(nullPrologue)
|
// precalculated hash of the hash of the Noise name = mixHash(nullPrologue)
|
||||||
private static final byte[] INIT_HASH_XK = new byte[32];
|
private static final byte[] INIT_HASH_XK = new byte[32];
|
||||||
private static final byte[] INIT_HASH_IK = new byte[32];
|
private static final byte[] INIT_HASH_IK = new byte[32];
|
||||||
private static final byte[] INIT_HASH_N = new byte[32];
|
private static final byte[] INIT_HASH_N = new byte[32];
|
||||||
private static final byte[] INIT_HASH_XK_SSU2 = new byte[32];
|
private static final byte[] INIT_HASH_XK_SSU2 = new byte[32];
|
||||||
|
private static final byte[] INIT_HASH_IKHFS_512 = new byte[32];
|
||||||
|
private static final byte[] INIT_HASH_IKHFS_768 = new byte[32];
|
||||||
|
private static final byte[] INIT_HASH_IKHFS_1024 = new byte[32];
|
||||||
|
|
||||||
static {
|
static {
|
||||||
INIT_CK_XK = initHash(HandshakeState.protocolName);
|
INIT_CK_XK = initHash(HandshakeState.protocolName);
|
||||||
INIT_CK_IK = initHash(HandshakeState.protocolName2);
|
INIT_CK_IK = initHash(HandshakeState.protocolName2);
|
||||||
INIT_CK_N = initHash(HandshakeState.protocolName3);
|
INIT_CK_N = initHash(HandshakeState.protocolName3);
|
||||||
INIT_CK_XK_SSU2 = initHash(HandshakeState.protocolName4);
|
INIT_CK_XK_SSU2 = initHash(HandshakeState.protocolName4);
|
||||||
|
INIT_CK_IKHFS_512 = initHash(HandshakeState.protocolName5);
|
||||||
|
INIT_CK_IKHFS_768 = initHash(HandshakeState.protocolName6);
|
||||||
|
INIT_CK_IKHFS_1024 = initHash(HandshakeState.protocolName7);
|
||||||
try {
|
try {
|
||||||
MessageDigest md = Noise.createHash("SHA256");
|
MessageDigest md = Noise.createHash("SHA256");
|
||||||
md.update(INIT_CK_XK, 0, 32);
|
md.update(INIT_CK_XK, 0, 32);
|
||||||
@ -62,6 +71,12 @@ class SymmetricState implements Destroyable, Cloneable {
|
|||||||
md.digest(INIT_HASH_N, 0, 32);
|
md.digest(INIT_HASH_N, 0, 32);
|
||||||
md.update(INIT_CK_XK_SSU2, 0, 32);
|
md.update(INIT_CK_XK_SSU2, 0, 32);
|
||||||
md.digest(INIT_HASH_XK_SSU2, 0, 32);
|
md.digest(INIT_HASH_XK_SSU2, 0, 32);
|
||||||
|
md.update(INIT_CK_IKHFS_512, 0, 32);
|
||||||
|
md.digest(INIT_HASH_IKHFS_512, 0, 32);
|
||||||
|
md.update(INIT_CK_IKHFS_768, 0, 32);
|
||||||
|
md.digest(INIT_HASH_IKHFS_768, 0, 32);
|
||||||
|
md.update(INIT_CK_IKHFS_1024, 0, 32);
|
||||||
|
md.digest(INIT_HASH_IKHFS_1024, 0, 32);
|
||||||
Noise.releaseHash(md);
|
Noise.releaseHash(md);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
@ -136,6 +151,15 @@ class SymmetricState implements Destroyable, Cloneable {
|
|||||||
} else if (patternId.equals(HandshakeState.PATTERN_ID_XK_SSU2)) {
|
} else if (patternId.equals(HandshakeState.PATTERN_ID_XK_SSU2)) {
|
||||||
initCK = INIT_CK_XK_SSU2;
|
initCK = INIT_CK_XK_SSU2;
|
||||||
initHash = INIT_HASH_XK_SSU2;
|
initHash = INIT_HASH_XK_SSU2;
|
||||||
|
} else if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_512)) {
|
||||||
|
initCK = INIT_CK_IKHFS_512;
|
||||||
|
initHash = INIT_HASH_IKHFS_512;
|
||||||
|
} else if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_768)) {
|
||||||
|
initCK = INIT_CK_IKHFS_768;
|
||||||
|
initHash = INIT_HASH_IKHFS_768;
|
||||||
|
} else if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_1024)) {
|
||||||
|
initCK = INIT_CK_IKHFS_1024;
|
||||||
|
initHash = INIT_HASH_IKHFS_1024;
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Handshake pattern is not recognized");
|
throw new IllegalArgumentException("Handshake pattern is not recognized");
|
||||||
}
|
}
|
||||||
@ -319,6 +343,7 @@ class SymmetricState implements Destroyable, Cloneable {
|
|||||||
*/
|
*/
|
||||||
public int decryptAndHash(byte[] ciphertext, int ciphertextOffset, byte[] plaintext, int plaintextOffset, int length) throws ShortBufferException, BadPaddingException
|
public int decryptAndHash(byte[] ciphertext, int ciphertextOffset, byte[] plaintext, int plaintextOffset, int length) throws ShortBufferException, BadPaddingException
|
||||||
{
|
{
|
||||||
|
// NOTE: This updates the hash, even on failure
|
||||||
System.arraycopy(h, 0, prev_h, 0, h.length);
|
System.arraycopy(h, 0, prev_h, 0, h.length);
|
||||||
mixHash(ciphertext, ciphertextOffset, length);
|
mixHash(ciphertext, ciphertextOffset, length);
|
||||||
return cipher.decryptWithAd(prev_h, ciphertext, ciphertextOffset, plaintext, plaintextOffset, length);
|
return cipher.decryptWithAd(prev_h, ciphertext, ciphertextOffset, plaintext, plaintextOffset, length);
|
||||||
|
@ -26,6 +26,7 @@ public class LeaseSetKeys {
|
|||||||
private final SigningPrivateKey _revocationKey;
|
private final SigningPrivateKey _revocationKey;
|
||||||
private final PrivateKey _decryptionKey;
|
private final PrivateKey _decryptionKey;
|
||||||
private final PrivateKey _decryptionKeyEC;
|
private final PrivateKey _decryptionKeyEC;
|
||||||
|
private final PrivateKey _decryptionKeyPQ;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unmodifiable, ElGamal only
|
* Unmodifiable, ElGamal only
|
||||||
@ -43,6 +44,41 @@ public class LeaseSetKeys {
|
|||||||
*/
|
*/
|
||||||
public static final Set<EncType> SET_BOTH = Collections.unmodifiableSet(EnumSet.of(EncType.ELGAMAL_2048, EncType.ECIES_X25519));
|
public static final Set<EncType> SET_BOTH = Collections.unmodifiableSet(EnumSet.of(EncType.ELGAMAL_2048, EncType.ECIES_X25519));
|
||||||
private static final Set<EncType> SET_NONE = Collections.emptySet();
|
private static final Set<EncType> SET_NONE = Collections.emptySet();
|
||||||
|
/**
|
||||||
|
* Unmodifiable, PQ only
|
||||||
|
* @since public since 0.9.67
|
||||||
|
*/
|
||||||
|
public static final Set<EncType> SET_PQ1 = Collections.unmodifiableSet(EnumSet.of(EncType.MLKEM512_X25519));
|
||||||
|
/**
|
||||||
|
* Unmodifiable, PQ only
|
||||||
|
* @since public since 0.9.67
|
||||||
|
*/
|
||||||
|
public static final Set<EncType> SET_PQ2 = Collections.unmodifiableSet(EnumSet.of(EncType.MLKEM768_X25519));
|
||||||
|
/**
|
||||||
|
* Unmodifiable, PQ only
|
||||||
|
* @since public since 0.9.67
|
||||||
|
*/
|
||||||
|
public static final Set<EncType> SET_PQ3 = Collections.unmodifiableSet(EnumSet.of(EncType.MLKEM1024_X25519));
|
||||||
|
/**
|
||||||
|
* Unmodifiable, ECIES-X25519 and PQ only
|
||||||
|
* @since public since 0.9.67
|
||||||
|
*/
|
||||||
|
public static final Set<EncType> SET_EC_PQ1 = Collections.unmodifiableSet(EnumSet.of(EncType.ECIES_X25519, EncType.MLKEM512_X25519));
|
||||||
|
/**
|
||||||
|
* Unmodifiable, ECIES-X25519 and PQ only
|
||||||
|
* @since public since 0.9.67
|
||||||
|
*/
|
||||||
|
public static final Set<EncType> SET_EC_PQ2 = Collections.unmodifiableSet(EnumSet.of(EncType.ECIES_X25519, EncType.MLKEM768_X25519));
|
||||||
|
/**
|
||||||
|
* Unmodifiable, ECIES-X25519 and PQ only
|
||||||
|
* @since public since 0.9.67
|
||||||
|
*/
|
||||||
|
public static final Set<EncType> SET_EC_PQ3 = Collections.unmodifiableSet(EnumSet.of(EncType.ECIES_X25519, EncType.MLKEM1024_X25519));
|
||||||
|
/**
|
||||||
|
* Unmodifiable, ECIES-X25519 and PQ only
|
||||||
|
* @since public since 0.9.67
|
||||||
|
*/
|
||||||
|
public static final Set<EncType> SET_EC_PQ_ALL = Collections.unmodifiableSet(EnumSet.of(EncType.ECIES_X25519, EncType.MLKEM512_X25519, EncType.MLKEM768_X25519, EncType.MLKEM1024_X25519));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client with a single key
|
* Client with a single key
|
||||||
@ -57,9 +93,15 @@ public class LeaseSetKeys {
|
|||||||
if (type == EncType.ELGAMAL_2048) {
|
if (type == EncType.ELGAMAL_2048) {
|
||||||
_decryptionKey = decryptionKey;
|
_decryptionKey = decryptionKey;
|
||||||
_decryptionKeyEC = null;
|
_decryptionKeyEC = null;
|
||||||
|
_decryptionKeyPQ = null;
|
||||||
} else if (type == EncType.ECIES_X25519) {
|
} else if (type == EncType.ECIES_X25519) {
|
||||||
_decryptionKey = null;
|
_decryptionKey = null;
|
||||||
_decryptionKeyEC = decryptionKey;
|
_decryptionKeyEC = decryptionKey;
|
||||||
|
_decryptionKeyPQ = null;
|
||||||
|
} else if (type.isPQ()) {
|
||||||
|
_decryptionKey = null;
|
||||||
|
_decryptionKeyEC =null;
|
||||||
|
_decryptionKeyPQ = decryptionKey;
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Unknown type " + type);
|
throw new IllegalArgumentException("Unknown type " + type);
|
||||||
}
|
}
|
||||||
@ -68,9 +110,13 @@ public class LeaseSetKeys {
|
|||||||
/**
|
/**
|
||||||
* Client with multiple keys
|
* Client with multiple keys
|
||||||
*
|
*
|
||||||
|
* The ONLY valid combinations are X25519 + ElG or X25519 + (MLKEM512 OR MLKEM768 OR MLKEM1024).
|
||||||
|
* Other combinations will throw IllegalArgumentException.
|
||||||
|
*
|
||||||
* @param dest unused
|
* @param dest unused
|
||||||
* @param revocationKey unused, may be null
|
* @param revocationKey unused, may be null
|
||||||
* @param decryptionKeys non-null, non-empty
|
* @param decryptionKeys non-null, non-empty
|
||||||
|
* @throws IllegalArgumentException
|
||||||
* @since 0.9.44
|
* @since 0.9.44
|
||||||
*/
|
*/
|
||||||
public LeaseSetKeys(Destination dest, SigningPrivateKey revocationKey, List<PrivateKey> decryptionKeys) {
|
public LeaseSetKeys(Destination dest, SigningPrivateKey revocationKey, List<PrivateKey> decryptionKeys) {
|
||||||
@ -79,22 +125,32 @@ public class LeaseSetKeys {
|
|||||||
_revocationKey = revocationKey;
|
_revocationKey = revocationKey;
|
||||||
PrivateKey elg = null;
|
PrivateKey elg = null;
|
||||||
PrivateKey ec = null;
|
PrivateKey ec = null;
|
||||||
|
PrivateKey pq = null;
|
||||||
for (PrivateKey pk : decryptionKeys) {
|
for (PrivateKey pk : decryptionKeys) {
|
||||||
EncType type = pk.getType();
|
EncType type = pk.getType();
|
||||||
if (type == EncType.ELGAMAL_2048) {
|
if (type == EncType.ELGAMAL_2048) {
|
||||||
if (elg != null)
|
if (elg != null)
|
||||||
throw new IllegalArgumentException("Multiple keys same type");
|
throw new IllegalArgumentException("Multiple keys same type");
|
||||||
|
if (pq != null)
|
||||||
|
throw new IllegalArgumentException("Invalid combination ElG + PQ");
|
||||||
elg = pk;
|
elg = pk;
|
||||||
} else if (type == EncType.ECIES_X25519) {
|
} else if (type == EncType.ECIES_X25519) {
|
||||||
if (ec != null)
|
if (ec != null)
|
||||||
throw new IllegalArgumentException("Multiple keys same type");
|
throw new IllegalArgumentException("Multiple keys same type");
|
||||||
ec = pk;
|
ec = pk;
|
||||||
|
} else if (type.isPQ()) {
|
||||||
|
if (pq != null)
|
||||||
|
throw new IllegalArgumentException("Multiple keys same type");
|
||||||
|
if (elg != null)
|
||||||
|
throw new IllegalArgumentException("Invalid combination ElG + PQ");
|
||||||
|
pq = pk;
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Unknown type " + type);
|
throw new IllegalArgumentException("Unknown type " + type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_decryptionKey = elg;
|
_decryptionKey = elg;
|
||||||
_decryptionKeyEC = ec;
|
_decryptionKeyEC = ec;
|
||||||
|
_decryptionKeyPQ = pq;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -128,9 +184,17 @@ public class LeaseSetKeys {
|
|||||||
return _decryptionKey;
|
return _decryptionKey;
|
||||||
if (type == EncType.ECIES_X25519)
|
if (type == EncType.ECIES_X25519)
|
||||||
return _decryptionKeyEC;
|
return _decryptionKeyEC;
|
||||||
|
if (type.isPQ() && _decryptionKeyPQ != null && _decryptionKeyPQ.getType() == type)
|
||||||
|
return _decryptionKeyPQ;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return PQ key (any type) or null if the LS does not support PQ
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
public PrivateKey getPQDecryptionKey() { return _decryptionKeyPQ; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do we support this type of encryption?
|
* Do we support this type of encryption?
|
||||||
*
|
*
|
||||||
@ -141,6 +205,8 @@ public class LeaseSetKeys {
|
|||||||
return _decryptionKey != null;
|
return _decryptionKey != null;
|
||||||
if (type == EncType.ECIES_X25519)
|
if (type == EncType.ECIES_X25519)
|
||||||
return _decryptionKeyEC != null;
|
return _decryptionKeyEC != null;
|
||||||
|
if (type.isPQ())
|
||||||
|
return _decryptionKeyPQ != null && _decryptionKeyPQ.getType() == type;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,6 +218,27 @@ public class LeaseSetKeys {
|
|||||||
public Set<EncType> getSupportedEncryption() {
|
public Set<EncType> getSupportedEncryption() {
|
||||||
if (_decryptionKey != null)
|
if (_decryptionKey != null)
|
||||||
return (_decryptionKeyEC != null) ? SET_BOTH : SET_ELG;
|
return (_decryptionKeyEC != null) ? SET_BOTH : SET_ELG;
|
||||||
|
if (_decryptionKeyPQ != null) {
|
||||||
|
if (_decryptionKeyEC != null) {
|
||||||
|
switch (_decryptionKeyPQ.getType()) {
|
||||||
|
case MLKEM512_X25519:
|
||||||
|
return SET_EC_PQ1;
|
||||||
|
case MLKEM768_X25519:
|
||||||
|
return SET_EC_PQ2;
|
||||||
|
case MLKEM1024_X25519:
|
||||||
|
return SET_EC_PQ3;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (_decryptionKeyPQ.getType()) {
|
||||||
|
case MLKEM512_X25519:
|
||||||
|
return SET_PQ1;
|
||||||
|
case MLKEM768_X25519:
|
||||||
|
return SET_PQ2;
|
||||||
|
case MLKEM1024_X25519:
|
||||||
|
return SET_PQ3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return (_decryptionKeyEC != null) ? SET_EC : SET_NONE;
|
return (_decryptionKeyEC != null) ? SET_EC : SET_NONE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import java.util.Set;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import net.i2p.client.I2PClient;
|
import net.i2p.client.I2PClient;
|
||||||
|
import net.i2p.crypto.EncType;
|
||||||
import net.i2p.crypto.SessionKeyManager;
|
import net.i2p.crypto.SessionKeyManager;
|
||||||
import net.i2p.data.DatabaseEntry;
|
import net.i2p.data.DatabaseEntry;
|
||||||
import net.i2p.data.DataHelper;
|
import net.i2p.data.DataHelper;
|
||||||
@ -50,6 +51,7 @@ import net.i2p.router.JobImpl;
|
|||||||
import net.i2p.router.RouterContext;
|
import net.i2p.router.RouterContext;
|
||||||
import net.i2p.router.crypto.TransientSessionKeyManager;
|
import net.i2p.router.crypto.TransientSessionKeyManager;
|
||||||
import net.i2p.router.crypto.ratchet.RatchetSKM;
|
import net.i2p.router.crypto.ratchet.RatchetSKM;
|
||||||
|
import net.i2p.router.crypto.ratchet.MuxedPQSKM;
|
||||||
import net.i2p.router.crypto.ratchet.MuxedSKM;
|
import net.i2p.router.crypto.ratchet.MuxedSKM;
|
||||||
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
|
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
|
||||||
import net.i2p.util.ConcurrentHashSet;
|
import net.i2p.util.ConcurrentHashSet;
|
||||||
@ -605,6 +607,8 @@ class ClientConnectionRunner {
|
|||||||
int thresh = TransientSessionKeyManager.LOW_THRESHOLD;
|
int thresh = TransientSessionKeyManager.LOW_THRESHOLD;
|
||||||
boolean hasElg = false;
|
boolean hasElg = false;
|
||||||
boolean hasEC = false;
|
boolean hasEC = false;
|
||||||
|
boolean hasPQ = false;
|
||||||
|
int pqType = 0;
|
||||||
// router may be null in unit tests, avoid NPEs in ratchet
|
// router may be null in unit tests, avoid NPEs in ratchet
|
||||||
// we won't actually be using any SKM anyway
|
// we won't actually be using any SKM anyway
|
||||||
if (opts != null && _context.router() != null) {
|
if (opts != null && _context.router() != null) {
|
||||||
@ -620,10 +624,18 @@ class ClientConnectionRunner {
|
|||||||
if (senc != null) {
|
if (senc != null) {
|
||||||
String[] senca = DataHelper.split(senc, ",");
|
String[] senca = DataHelper.split(senc, ",");
|
||||||
for (String sencaa : senca) {
|
for (String sencaa : senca) {
|
||||||
if (sencaa.equals("0"))
|
if (sencaa.equals("0")) {
|
||||||
hasElg = true;
|
hasElg = true;
|
||||||
else if (sencaa.equals("4"))
|
} else if (sencaa.equals("4")) {
|
||||||
hasEC = true;
|
hasEC = true;
|
||||||
|
} else if (sencaa.equals("5") || sencaa.equals("6") || sencaa.equals("7")) {
|
||||||
|
if (hasPQ) {
|
||||||
|
_log.error("Bad encryption type combination in i2cp.leaseSetEncType for " + dest.toBase32());
|
||||||
|
return SessionStatusMessage.STATUS_INVALID;
|
||||||
|
}
|
||||||
|
pqType = Integer.parseInt(sencaa);
|
||||||
|
hasPQ = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
hasElg = true;
|
hasElg = true;
|
||||||
@ -632,6 +644,10 @@ class ClientConnectionRunner {
|
|||||||
hasElg = true;
|
hasElg = true;
|
||||||
}
|
}
|
||||||
if (hasElg) {
|
if (hasElg) {
|
||||||
|
if (hasPQ) {
|
||||||
|
_log.error("Bad encryption type combination in i2cp.leaseSetEncType for " + dest.toBase32());
|
||||||
|
return SessionStatusMessage.STATUS_INVALID;
|
||||||
|
}
|
||||||
TransientSessionKeyManager tskm = new TransientSessionKeyManager(_context, tags, thresh);
|
TransientSessionKeyManager tskm = new TransientSessionKeyManager(_context, tags, thresh);
|
||||||
if (hasEC) {
|
if (hasEC) {
|
||||||
RatchetSKM rskm = new RatchetSKM(_context, dest);
|
RatchetSKM rskm = new RatchetSKM(_context, dest);
|
||||||
@ -639,6 +655,16 @@ class ClientConnectionRunner {
|
|||||||
} else {
|
} else {
|
||||||
_sessionKeyManager = tskm;
|
_sessionKeyManager = tskm;
|
||||||
}
|
}
|
||||||
|
} else if (hasPQ) {
|
||||||
|
if (hasEC) {
|
||||||
|
// ECIES
|
||||||
|
RatchetSKM rskm1 = new RatchetSKM(_context, dest);
|
||||||
|
// PQ
|
||||||
|
RatchetSKM rskm2 = new RatchetSKM(_context, dest, EncType.getByCode(pqType));
|
||||||
|
_sessionKeyManager = new MuxedPQSKM(rskm1, rskm2);
|
||||||
|
} else {
|
||||||
|
_sessionKeyManager = new RatchetSKM(_context, dest, EncType.getByCode(pqType));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (hasEC) {
|
if (hasEC) {
|
||||||
_sessionKeyManager = new RatchetSKM(_context, dest);
|
_sessionKeyManager = new RatchetSKM(_context, dest);
|
||||||
|
@ -16,6 +16,7 @@ import com.southernstorm.noise.protocol.HandshakeState;
|
|||||||
|
|
||||||
import net.i2p.crypto.EncType;
|
import net.i2p.crypto.EncType;
|
||||||
import net.i2p.crypto.HKDF;
|
import net.i2p.crypto.HKDF;
|
||||||
|
import net.i2p.crypto.KeyFactory;
|
||||||
import net.i2p.crypto.SessionKeyManager;
|
import net.i2p.crypto.SessionKeyManager;
|
||||||
import net.i2p.data.Base64;
|
import net.i2p.data.Base64;
|
||||||
import net.i2p.data.Certificate;
|
import net.i2p.data.Certificate;
|
||||||
@ -33,6 +34,7 @@ import net.i2p.data.SessionTag;
|
|||||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||||
import net.i2p.data.i2np.GarlicClove;
|
import net.i2p.data.i2np.GarlicClove;
|
||||||
import net.i2p.data.i2np.I2NPMessage;
|
import net.i2p.data.i2np.I2NPMessage;
|
||||||
|
import net.i2p.router.crypto.pqc.MLKEM;
|
||||||
import static net.i2p.router.crypto.ratchet.RatchetPayload.*;
|
import static net.i2p.router.crypto.ratchet.RatchetPayload.*;
|
||||||
import net.i2p.router.LeaseSetKeys;
|
import net.i2p.router.LeaseSetKeys;
|
||||||
import net.i2p.router.Router;
|
import net.i2p.router.Router;
|
||||||
@ -53,6 +55,7 @@ public final class ECIESAEADEngine {
|
|||||||
private final RouterContext _context;
|
private final RouterContext _context;
|
||||||
private final Log _log;
|
private final Log _log;
|
||||||
private final MuxedEngine _muxedEngine;
|
private final MuxedEngine _muxedEngine;
|
||||||
|
private final MuxedPQEngine _muxedPQEngine;
|
||||||
private final HKDF _hkdf;
|
private final HKDF _hkdf;
|
||||||
private final Elg2KeyFactory _edhThread;
|
private final Elg2KeyFactory _edhThread;
|
||||||
private boolean _isRunning;
|
private boolean _isRunning;
|
||||||
@ -86,6 +89,24 @@ public final class ECIESAEADEngine {
|
|||||||
private static final String INFO_0 = "SessionReplyTags";
|
private static final String INFO_0 = "SessionReplyTags";
|
||||||
private static final String INFO_6 = "AttachPayloadKDF";
|
private static final String INFO_6 = "AttachPayloadKDF";
|
||||||
|
|
||||||
|
// These are the min sizes for the MLKEM New Session Message.
|
||||||
|
// It contains an extra MLKEM key and MAC.
|
||||||
|
// 112
|
||||||
|
private static final int NS_MLKEM_OVERHEAD = NS_OVERHEAD + MACLEN;
|
||||||
|
// 800 + 112 + 7 = 919
|
||||||
|
private static final int MIN_NS_MLKEM512_SIZE = EncType.MLKEM512_X25519_INT.getPubkeyLen() + NS_MLKEM_OVERHEAD + DATETIME_SIZE;
|
||||||
|
// 1184 + 112 + 7 = 1303
|
||||||
|
private static final int MIN_NS_MLKEM768_SIZE = EncType.MLKEM768_X25519_INT.getPubkeyLen() + NS_MLKEM_OVERHEAD + DATETIME_SIZE;
|
||||||
|
// 1568 + 112 + 7 = 1687
|
||||||
|
private static final int MIN_NS_MLKEM1024_SIZE = EncType.MLKEM1024_X25519_INT.getPubkeyLen() + NS_MLKEM_OVERHEAD + DATETIME_SIZE;
|
||||||
|
// 856
|
||||||
|
private static final int MIN_NSR_MLKEM512_SIZE = EncType.MLKEM512_X25519_CT.getPubkeyLen() + MIN_NSR_SIZE + MACLEN;
|
||||||
|
// 1176
|
||||||
|
private static final int MIN_NSR_MLKEM768_SIZE = EncType.MLKEM768_X25519_CT.getPubkeyLen() + MIN_NSR_SIZE + MACLEN;
|
||||||
|
// 1656
|
||||||
|
private static final int MIN_NSR_MLKEM1024_SIZE = EncType.MLKEM1024_X25519_CT.getPubkeyLen() + MIN_NSR_SIZE + MACLEN;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Caller MUST call startup() to get threaded generation.
|
* Caller MUST call startup() to get threaded generation.
|
||||||
* Will still work without, will just generate inline.
|
* Will still work without, will just generate inline.
|
||||||
@ -96,6 +117,7 @@ public final class ECIESAEADEngine {
|
|||||||
_context = ctx;
|
_context = ctx;
|
||||||
_log = _context.logManager().getLog(ECIESAEADEngine.class);
|
_log = _context.logManager().getLog(ECIESAEADEngine.class);
|
||||||
_muxedEngine = new MuxedEngine(ctx);
|
_muxedEngine = new MuxedEngine(ctx);
|
||||||
|
_muxedPQEngine = new MuxedPQEngine(ctx);
|
||||||
_hkdf = new HKDF(ctx);
|
_hkdf = new HKDF(ctx);
|
||||||
_edhThread = new Elg2KeyFactory(ctx);
|
_edhThread = new Elg2KeyFactory(ctx);
|
||||||
|
|
||||||
@ -147,6 +169,18 @@ public final class ECIESAEADEngine {
|
|||||||
return _muxedEngine.decrypt(data, elgKey, ecKey, keyManager);
|
return _muxedEngine.decrypt(data, elgKey, ecKey, keyManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to decrypt the message with one or both of the given private keys
|
||||||
|
*
|
||||||
|
* @param ecKey must be EC, non-null
|
||||||
|
* @param pqKey must be PQ, non-null
|
||||||
|
* @return decrypted data or null on failure
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
public CloveSet decrypt(byte data[], PrivateKey ecKey, PrivateKey pqKey, MuxedPQSKM keyManager) throws DataFormatException {
|
||||||
|
return _muxedPQEngine.decrypt(data, ecKey, pqKey, keyManager);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt the message using the given private key
|
* Decrypt the message using the given private key
|
||||||
* and using tags from the specified key manager.
|
* and using tags from the specified key manager.
|
||||||
@ -175,8 +209,7 @@ public final class ECIESAEADEngine {
|
|||||||
|
|
||||||
private CloveSet x_decrypt(byte data[], PrivateKey targetPrivateKey,
|
private CloveSet x_decrypt(byte data[], PrivateKey targetPrivateKey,
|
||||||
RatchetSKM keyManager) throws DataFormatException {
|
RatchetSKM keyManager) throws DataFormatException {
|
||||||
if (targetPrivateKey.getType() != EncType.ECIES_X25519)
|
checkType(targetPrivateKey.getType());
|
||||||
throw new IllegalArgumentException();
|
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
if (_log.shouldLog(Log.ERROR)) _log.error("Null data being decrypted?");
|
if (_log.shouldLog(Log.ERROR)) _log.error("Null data being decrypted?");
|
||||||
return null;
|
return null;
|
||||||
@ -267,14 +300,22 @@ public final class ECIESAEADEngine {
|
|||||||
if (shouldDebug)
|
if (shouldDebug)
|
||||||
_log.debug("Decrypting ES with tag: " + st.toBase64() + " key: " + key + ": " + data.length + " bytes");
|
_log.debug("Decrypting ES with tag: " + st.toBase64() + " key: " + key + ": " + data.length + " bytes");
|
||||||
decrypted = decryptExistingSession(tag, data, key, targetPrivateKey, keyManager);
|
decrypted = decryptExistingSession(tag, data, key, targetPrivateKey, keyManager);
|
||||||
} else if (data.length >= MIN_NSR_SIZE) {
|
} else {
|
||||||
|
// it's important not to attempt decryption for too-short packets,
|
||||||
|
// because Noise will destroy() the handshake state on failure,
|
||||||
|
// and we don't clone() on the first one, so it's fatal.
|
||||||
|
EncType type = targetPrivateKey.getType();
|
||||||
|
int min = getMinNSRSize(type);
|
||||||
|
if (data.length >= min) {
|
||||||
if (shouldDebug)
|
if (shouldDebug)
|
||||||
_log.debug("Decrypting NSR with tag: " + st.toBase64() + " key: " + key + ": " + data.length + " bytes");
|
_log.debug("Decrypting NSR with tag: " + st.toBase64() + " key: " + key + ": " + data.length + " bytes");
|
||||||
decrypted = decryptNewSessionReply(tag, data, state, keyManager);
|
decrypted = decryptNewSessionReply(tag, data, state, keyManager);
|
||||||
} else {
|
} else {
|
||||||
decrypted = null;
|
decrypted = null;
|
||||||
if (_log.shouldWarn())
|
if (_log.shouldWarn())
|
||||||
_log.warn("ECIES decrypt fail, tag found but no state and too small for NSR: " + data.length + " bytes");
|
_log.warn("NSR decrypt fail, tag: " + st.toBase64() + " but packet too small: " + data.length + " bytes, min is " +
|
||||||
|
min + " on state " + state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (decrypted != null) {
|
if (decrypted != null) {
|
||||||
_context.statManager().updateFrequency("crypto.eciesAEAD.decryptExistingSession");
|
_context.statManager().updateFrequency("crypto.eciesAEAD.decryptExistingSession");
|
||||||
@ -316,8 +357,10 @@ public final class ECIESAEADEngine {
|
|||||||
private CloveSet x_decryptSlow(byte data[], PrivateKey targetPrivateKey,
|
private CloveSet x_decryptSlow(byte data[], PrivateKey targetPrivateKey,
|
||||||
RatchetSKM keyManager) throws DataFormatException {
|
RatchetSKM keyManager) throws DataFormatException {
|
||||||
CloveSet decrypted;
|
CloveSet decrypted;
|
||||||
|
EncType type = targetPrivateKey.getType();
|
||||||
|
int minns = getMinNSSize(type);
|
||||||
boolean isRouter = keyManager.getDestination() == null;
|
boolean isRouter = keyManager.getDestination() == null;
|
||||||
if (data.length >= MIN_NS_SIZE || (isRouter && data.length >= MIN_NS_N_SIZE)) {
|
if (data.length >= minns || (isRouter && data.length >= MIN_NS_N_SIZE)) {
|
||||||
if (isRouter)
|
if (isRouter)
|
||||||
decrypted = decryptNewSession_N(data, targetPrivateKey, keyManager);
|
decrypted = decryptNewSession_N(data, targetPrivateKey, keyManager);
|
||||||
else
|
else
|
||||||
@ -333,11 +376,116 @@ public final class ECIESAEADEngine {
|
|||||||
} else {
|
} else {
|
||||||
decrypted = null;
|
decrypted = null;
|
||||||
if (_log.shouldDebug())
|
if (_log.shouldDebug())
|
||||||
_log.debug("ECIES decrypt fail, too small for NS: " + data.length + " bytes");
|
_log.debug("ECIES decrypt fail, too small for NS: " + data.length + " bytes, min is " +
|
||||||
|
minns + " for type " + type);
|
||||||
}
|
}
|
||||||
return decrypted;
|
return decrypted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws IllegalArgumentException if unsupported
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
private static void checkType(EncType type) {
|
||||||
|
switch(type) {
|
||||||
|
case ECIES_X25519:
|
||||||
|
case MLKEM512_X25519:
|
||||||
|
case MLKEM768_X25519:
|
||||||
|
case MLKEM1024_X25519:
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unsupported key type " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
private static String getNoisePattern(EncType type) {
|
||||||
|
switch(type) {
|
||||||
|
case ECIES_X25519:
|
||||||
|
return HandshakeState.PATTERN_ID_IK;
|
||||||
|
case MLKEM512_X25519:
|
||||||
|
return HandshakeState.PATTERN_ID_IKHFS_512;
|
||||||
|
case MLKEM768_X25519:
|
||||||
|
return HandshakeState.PATTERN_ID_IKHFS_768;
|
||||||
|
case MLKEM1024_X25519:
|
||||||
|
return HandshakeState.PATTERN_ID_IKHFS_1024;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("No pattern for " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
private static KeyFactory getHybridKeyFactory(EncType type) {
|
||||||
|
switch(type) {
|
||||||
|
case MLKEM512_X25519:
|
||||||
|
return MLKEM.MLKEM512KeyFactory;
|
||||||
|
case MLKEM768_X25519:
|
||||||
|
return MLKEM.MLKEM768KeyFactory;
|
||||||
|
case MLKEM1024_X25519:
|
||||||
|
return MLKEM.MLKEM1024KeyFactory;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
private static int getMinNSSize(EncType type) {
|
||||||
|
switch(type) {
|
||||||
|
case ECIES_X25519:
|
||||||
|
return MIN_NS_SIZE;
|
||||||
|
case MLKEM512_X25519:
|
||||||
|
return MIN_NS_MLKEM512_SIZE;
|
||||||
|
case MLKEM768_X25519:
|
||||||
|
return MIN_NS_MLKEM768_SIZE;
|
||||||
|
case MLKEM1024_X25519:
|
||||||
|
return MIN_NS_MLKEM1024_SIZE;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("No pattern for " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
private static int getMinNSRSize(EncType type) {
|
||||||
|
switch(type) {
|
||||||
|
case ECIES_X25519:
|
||||||
|
return MIN_NSR_SIZE;
|
||||||
|
case MLKEM512_X25519:
|
||||||
|
return MIN_NSR_MLKEM512_SIZE;
|
||||||
|
case MLKEM768_X25519:
|
||||||
|
return MIN_NSR_MLKEM768_SIZE;
|
||||||
|
case MLKEM1024_X25519:
|
||||||
|
return MIN_NSR_MLKEM1024_SIZE;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("No pattern for " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
private static Set<EncType> getEncTypeSet(EncType type) {
|
||||||
|
switch(type) {
|
||||||
|
case ECIES_X25519:
|
||||||
|
return LeaseSetKeys.SET_EC;
|
||||||
|
case MLKEM512_X25519:
|
||||||
|
return LeaseSetKeys.SET_PQ1;
|
||||||
|
case MLKEM768_X25519:
|
||||||
|
return LeaseSetKeys.SET_PQ2;
|
||||||
|
case MLKEM1024_X25519:
|
||||||
|
return LeaseSetKeys.SET_PQ3;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("No pattern for " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* scenario 1: New Session Message
|
* scenario 1: New Session Message
|
||||||
*
|
*
|
||||||
@ -383,8 +531,12 @@ public final class ECIESAEADEngine {
|
|||||||
System.arraycopy(pk.getData(), 0, data, 0, KEYLEN);
|
System.arraycopy(pk.getData(), 0, data, 0, KEYLEN);
|
||||||
|
|
||||||
HandshakeState state;
|
HandshakeState state;
|
||||||
|
EncType type = targetPrivateKey.getType();
|
||||||
try {
|
try {
|
||||||
state = new HandshakeState(HandshakeState.PATTERN_ID_IK, HandshakeState.RESPONDER, _edhThread);
|
String pattern = getNoisePattern(type);
|
||||||
|
// Bob does not need a key factory
|
||||||
|
//state = new HandshakeState(pattern, HandshakeState.RESPONDER, _edhThread, getHybridKeyFactory(type));
|
||||||
|
state = new HandshakeState(pattern, HandshakeState.RESPONDER, _edhThread);
|
||||||
} catch (GeneralSecurityException gse) {
|
} catch (GeneralSecurityException gse) {
|
||||||
throw new IllegalStateException("bad proto", gse);
|
throw new IllegalStateException("bad proto", gse);
|
||||||
}
|
}
|
||||||
@ -392,9 +544,13 @@ public final class ECIESAEADEngine {
|
|||||||
targetPrivateKey.toPublic().getData(), 0);
|
targetPrivateKey.toPublic().getData(), 0);
|
||||||
state.start();
|
state.start();
|
||||||
if (_log.shouldDebug())
|
if (_log.shouldDebug())
|
||||||
_log.debug("State before decrypt new session: " + state);
|
_log.debug("State before decrypt new session (" + data.length + " bytes) " + state);
|
||||||
|
|
||||||
int payloadlen = data.length - (KEYLEN + KEYLEN + MACLEN + MACLEN);
|
int payloadlen = data.length - (KEYLEN + KEYLEN + MACLEN + MACLEN);
|
||||||
|
DHState hyb = state.getRemoteHybridKeyPair();
|
||||||
|
if (hyb != null) {
|
||||||
|
payloadlen -= hyb.getPublicKeyLength() + MACLEN;
|
||||||
|
}
|
||||||
byte[] payload = new byte[payloadlen];
|
byte[] payload = new byte[payloadlen];
|
||||||
try {
|
try {
|
||||||
state.readMessage(data, 0, data.length, payload, 0);
|
state.readMessage(data, 0, data.length, payload, 0);
|
||||||
@ -402,7 +558,7 @@ public final class ECIESAEADEngine {
|
|||||||
// we'll get this a lot on muxed SKM
|
// we'll get this a lot on muxed SKM
|
||||||
// logged at INFO in caller
|
// logged at INFO in caller
|
||||||
if (_log.shouldDebug())
|
if (_log.shouldDebug())
|
||||||
_log.debug("Decrypt fail NS, state at failure: " + state, gse);
|
_log.debug("Decrypt fail NS " + data.length + " bytes, state at failure: " + state, gse);
|
||||||
// restore original data for subsequent ElG attempt
|
// restore original data for subsequent ElG attempt
|
||||||
System.arraycopy(xx, 0, data, 0, KEYLEN - 1);
|
System.arraycopy(xx, 0, data, 0, KEYLEN - 1);
|
||||||
data[KEYLEN - 1] = xx31;
|
data[KEYLEN - 1] = xx31;
|
||||||
@ -467,7 +623,7 @@ public final class ECIESAEADEngine {
|
|||||||
state.destroy();
|
state.destroy();
|
||||||
} else {
|
} else {
|
||||||
// tell the SKM
|
// tell the SKM
|
||||||
PublicKey alice = new PublicKey(EncType.ECIES_X25519, alicePK);
|
PublicKey alice = new PublicKey(type, alicePK);
|
||||||
keyManager.createSession(alice, null, state, null);
|
keyManager.createSession(alice, null, state, null);
|
||||||
setResponseTimerNS(alice, pc.cloveSet, keyManager);
|
setResponseTimerNS(alice, pc.cloveSet, keyManager);
|
||||||
}
|
}
|
||||||
@ -638,8 +794,13 @@ public final class ECIESAEADEngine {
|
|||||||
state.mixHash(tag, 0, TAGLEN);
|
state.mixHash(tag, 0, TAGLEN);
|
||||||
if (_log.shouldDebug())
|
if (_log.shouldDebug())
|
||||||
_log.debug("State after mixhash tag before decrypt new session reply: " + state);
|
_log.debug("State after mixhash tag before decrypt new session reply: " + state);
|
||||||
|
int tmplen = 48;
|
||||||
|
DHState hyb = state.getRemoteHybridKeyPair();
|
||||||
|
if (hyb != null) {
|
||||||
|
tmplen += hyb.getPublicKeyLength() + MACLEN;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
state.readMessage(data, 8, 48, ZEROLEN, 0);
|
state.readMessage(data, 8, tmplen, ZEROLEN, 0);
|
||||||
} catch (GeneralSecurityException gse) {
|
} catch (GeneralSecurityException gse) {
|
||||||
if (_log.shouldWarn()) {
|
if (_log.shouldWarn()) {
|
||||||
_log.warn("Decrypt fail NSR part 1", gse);
|
_log.warn("Decrypt fail NSR part 1", gse);
|
||||||
@ -667,9 +828,16 @@ public final class ECIESAEADEngine {
|
|||||||
byte[] encpayloadkey = new byte[32];
|
byte[] encpayloadkey = new byte[32];
|
||||||
_hkdf.calculate(split.k_ba.getData(), ZEROLEN, INFO_6, encpayloadkey);
|
_hkdf.calculate(split.k_ba.getData(), ZEROLEN, INFO_6, encpayloadkey);
|
||||||
rcvr.initializeKey(encpayloadkey, 0);
|
rcvr.initializeKey(encpayloadkey, 0);
|
||||||
byte[] payload = new byte[data.length - (TAGLEN + KEYLEN + MACLEN + MACLEN)];
|
int off = TAGLEN + KEYLEN + MACLEN;
|
||||||
|
int plen = data.length - (TAGLEN + KEYLEN + MACLEN + MACLEN);
|
||||||
|
if (hyb != null) {
|
||||||
|
int len = hyb.getPublicKeyLength() + MACLEN;
|
||||||
|
off += len;
|
||||||
|
plen -= len;
|
||||||
|
}
|
||||||
|
byte[] payload = new byte[plen];
|
||||||
try {
|
try {
|
||||||
rcvr.decryptWithAd(hash, data, TAGLEN + KEYLEN + MACLEN, payload, 0, payload.length + MACLEN);
|
rcvr.decryptWithAd(hash, data, off, payload, 0, plen + MACLEN);
|
||||||
} catch (GeneralSecurityException gse) {
|
} catch (GeneralSecurityException gse) {
|
||||||
if (_log.shouldWarn()) {
|
if (_log.shouldWarn()) {
|
||||||
_log.warn("Decrypt fail NSR part 2", gse);
|
_log.warn("Decrypt fail NSR part 2", gse);
|
||||||
@ -714,7 +882,7 @@ public final class ECIESAEADEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// tell the SKM
|
// tell the SKM
|
||||||
PublicKey bob = new PublicKey(EncType.ECIES_X25519, bobPK);
|
PublicKey bob = new PublicKey(keyManager.getType(), bobPK);
|
||||||
keyManager.updateSession(bob, oldState, state, null, split);
|
keyManager.updateSession(bob, oldState, state, null, split);
|
||||||
|
|
||||||
if (pc == null)
|
if (pc == null)
|
||||||
@ -872,8 +1040,7 @@ public final class ECIESAEADEngine {
|
|||||||
private byte[] x_encrypt(CloveSet cloves, PublicKey target, Destination to, PrivateKey priv,
|
private byte[] x_encrypt(CloveSet cloves, PublicKey target, Destination to, PrivateKey priv,
|
||||||
RatchetSKM keyManager,
|
RatchetSKM keyManager,
|
||||||
ReplyCallback callback) {
|
ReplyCallback callback) {
|
||||||
if (target.getType() != EncType.ECIES_X25519)
|
checkType(target.getType());
|
||||||
throw new IllegalArgumentException();
|
|
||||||
if (Arrays.equals(target.getData(), NULLPK)) {
|
if (Arrays.equals(target.getData(), NULLPK)) {
|
||||||
if (_log.shouldWarn())
|
if (_log.shouldWarn())
|
||||||
_log.warn("Zero static key target");
|
_log.warn("Zero static key target");
|
||||||
@ -904,6 +1071,7 @@ public final class ECIESAEADEngine {
|
|||||||
}
|
}
|
||||||
if (_log.shouldDebug())
|
if (_log.shouldDebug())
|
||||||
_log.debug("Encrypting as NSR to " + target + " with tag " + re.tag.toBase64());
|
_log.debug("Encrypting as NSR to " + target + " with tag " + re.tag.toBase64());
|
||||||
|
// trash old state if this throws IAE???
|
||||||
return encryptNewSessionReply(cloves, target, state, re.tag, keyManager, callback);
|
return encryptNewSessionReply(cloves, target, state, re.tag, keyManager, callback);
|
||||||
}
|
}
|
||||||
byte rv[] = encryptExistingSession(cloves, target, re, callback, keyManager);
|
byte rv[] = encryptExistingSession(cloves, target, re, callback, keyManager);
|
||||||
@ -943,9 +1111,13 @@ public final class ECIESAEADEngine {
|
|||||||
private byte[] encryptNewSession(CloveSet cloves, PublicKey target, Destination to, PrivateKey priv,
|
private byte[] encryptNewSession(CloveSet cloves, PublicKey target, Destination to, PrivateKey priv,
|
||||||
RatchetSKM keyManager,
|
RatchetSKM keyManager,
|
||||||
ReplyCallback callback) {
|
ReplyCallback callback) {
|
||||||
|
EncType type = target.getType();
|
||||||
|
if (type != priv.getType())
|
||||||
|
throw new IllegalArgumentException("Key mismatch " + target + ' ' + priv);
|
||||||
HandshakeState state;
|
HandshakeState state;
|
||||||
try {
|
try {
|
||||||
state = new HandshakeState(HandshakeState.PATTERN_ID_IK, HandshakeState.INITIATOR, _edhThread);
|
String pattern = getNoisePattern(target.getType());
|
||||||
|
state = new HandshakeState(pattern, HandshakeState.INITIATOR, _edhThread, getHybridKeyFactory(type));
|
||||||
} catch (GeneralSecurityException gse) {
|
} catch (GeneralSecurityException gse) {
|
||||||
throw new IllegalStateException("bad proto", gse);
|
throw new IllegalStateException("bad proto", gse);
|
||||||
}
|
}
|
||||||
@ -962,7 +1134,12 @@ public final class ECIESAEADEngine {
|
|||||||
|
|
||||||
byte[] payload = createPayload(cloves, cloves.getExpiration(), NS_OVERHEAD);
|
byte[] payload = createPayload(cloves, cloves.getExpiration(), NS_OVERHEAD);
|
||||||
|
|
||||||
byte[] enc = new byte[KEYLEN + KEYLEN + MACLEN + payload.length + MACLEN];
|
int enclen = KEYLEN + KEYLEN + MACLEN + payload.length + MACLEN;
|
||||||
|
DHState hyb = state.getLocalHybridKeyPair();
|
||||||
|
if (hyb != null) {
|
||||||
|
enclen += hyb.getPublicKeyLength() + MACLEN;
|
||||||
|
}
|
||||||
|
byte[] enc = new byte[enclen];
|
||||||
try {
|
try {
|
||||||
state.writeMessage(enc, 0, payload, 0, payload.length);
|
state.writeMessage(enc, 0, payload, 0, payload.length);
|
||||||
} catch (GeneralSecurityException gse) {
|
} catch (GeneralSecurityException gse) {
|
||||||
@ -1071,7 +1248,12 @@ public final class ECIESAEADEngine {
|
|||||||
byte[] payload = createPayload(cloves, 0, NSR_OVERHEAD);
|
byte[] payload = createPayload(cloves, 0, NSR_OVERHEAD);
|
||||||
|
|
||||||
// part 1 - tag and empty payload
|
// part 1 - tag and empty payload
|
||||||
byte[] enc = new byte[TAGLEN + KEYLEN + MACLEN + payload.length + MACLEN];
|
int enclen = TAGLEN + KEYLEN + MACLEN + payload.length + MACLEN;
|
||||||
|
DHState hyb = state.getLocalHybridKeyPair();
|
||||||
|
if (hyb != null) {
|
||||||
|
enclen += hyb.getPublicKeyLength() + MACLEN;
|
||||||
|
}
|
||||||
|
byte[] enc = new byte[enclen];
|
||||||
System.arraycopy(tag, 0, enc, 0, TAGLEN);
|
System.arraycopy(tag, 0, enc, 0, TAGLEN);
|
||||||
try {
|
try {
|
||||||
state.writeMessage(enc, TAGLEN, ZEROLEN, 0, 0);
|
state.writeMessage(enc, TAGLEN, ZEROLEN, 0, 0);
|
||||||
@ -1103,8 +1285,12 @@ public final class ECIESAEADEngine {
|
|||||||
byte[] encpayloadkey = new byte[32];
|
byte[] encpayloadkey = new byte[32];
|
||||||
_hkdf.calculate(split.k_ba.getData(), ZEROLEN, INFO_6, encpayloadkey);
|
_hkdf.calculate(split.k_ba.getData(), ZEROLEN, INFO_6, encpayloadkey);
|
||||||
sender.initializeKey(encpayloadkey, 0);
|
sender.initializeKey(encpayloadkey, 0);
|
||||||
|
int off = TAGLEN + KEYLEN + MACLEN;
|
||||||
|
if (hyb != null) {
|
||||||
|
off += hyb.getPublicKeyLength() + MACLEN;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
sender.encryptWithAd(hash, payload, 0, enc, TAGLEN + KEYLEN + MACLEN, payload.length);
|
sender.encryptWithAd(hash, payload, 0, enc, off, payload.length);
|
||||||
} catch (GeneralSecurityException gse) {
|
} catch (GeneralSecurityException gse) {
|
||||||
if (_log.shouldWarn())
|
if (_log.shouldWarn())
|
||||||
_log.warn("Encrypt fail NSR part 2", gse);
|
_log.warn("Encrypt fail NSR part 2", gse);
|
||||||
@ -1488,7 +1674,7 @@ public final class ECIESAEADEngine {
|
|||||||
return;
|
return;
|
||||||
if (!ls2.isCurrent(Router.CLOCK_FUDGE_FACTOR))
|
if (!ls2.isCurrent(Router.CLOCK_FUDGE_FACTOR))
|
||||||
continue;
|
continue;
|
||||||
PublicKey pk = ls2.getEncryptionKey(LeaseSetKeys.SET_EC);
|
PublicKey pk = ls2.getEncryptionKey(getEncTypeSet(from.getType()));
|
||||||
if (!from.equals(pk))
|
if (!from.equals(pk))
|
||||||
continue;
|
continue;
|
||||||
if (!ls2.verifySignature())
|
if (!ls2.verifySignature())
|
||||||
|
@ -0,0 +1,98 @@
|
|||||||
|
package net.i2p.router.crypto.ratchet;
|
||||||
|
|
||||||
|
import net.i2p.crypto.EncAlgo;
|
||||||
|
import net.i2p.crypto.EncType;
|
||||||
|
import net.i2p.data.DataFormatException;
|
||||||
|
import net.i2p.data.PrivateKey;
|
||||||
|
import net.i2p.router.RouterContext;
|
||||||
|
import net.i2p.router.message.CloveSet;
|
||||||
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Both EC and PQ
|
||||||
|
*
|
||||||
|
* Handles the actual decryption using the
|
||||||
|
* supplied keys and data.
|
||||||
|
*
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
final class MuxedPQEngine {
|
||||||
|
private final RouterContext _context;
|
||||||
|
private final Log _log;
|
||||||
|
|
||||||
|
public MuxedPQEngine(RouterContext ctx) {
|
||||||
|
_context = ctx;
|
||||||
|
_log = _context.logManager().getLog(MuxedPQEngine.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt the message with the given private keys
|
||||||
|
*
|
||||||
|
* @param ecKey must be EC, non-null
|
||||||
|
* @param pqKey must be PQ, non-null
|
||||||
|
* @return decrypted data or null on failure
|
||||||
|
*/
|
||||||
|
public CloveSet decrypt(byte data[], PrivateKey ecKey, PrivateKey pqKey, MuxedPQSKM keyManager) throws DataFormatException {
|
||||||
|
if (ecKey.getType() != EncType.ECIES_X25519 ||
|
||||||
|
pqKey.getType().getBaseAlgorithm() != EncAlgo.ECIES_MLKEM)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
final boolean debug = _log.shouldDebug();
|
||||||
|
CloveSet rv = null;
|
||||||
|
// Try in-order from fastest to slowest
|
||||||
|
boolean preferRatchet = keyManager.preferRatchet();
|
||||||
|
if (preferRatchet) {
|
||||||
|
// Ratchet Tag
|
||||||
|
rv = _context.eciesEngine().decryptFast(data, ecKey, keyManager.getECSKM());
|
||||||
|
if (rv != null)
|
||||||
|
return rv;
|
||||||
|
if (debug)
|
||||||
|
_log.debug("Ratchet tag not found before PQ");
|
||||||
|
}
|
||||||
|
// PQ
|
||||||
|
// Ratchet Tag
|
||||||
|
rv = _context.eciesEngine().decryptFast(data, pqKey, keyManager.getPQSKM());
|
||||||
|
if (rv != null)
|
||||||
|
return rv;
|
||||||
|
if (debug)
|
||||||
|
_log.debug("PQ tag not found");
|
||||||
|
if (!preferRatchet) {
|
||||||
|
// Ratchet Tag
|
||||||
|
rv = _context.eciesEngine().decryptFast(data, ecKey, keyManager.getECSKM());
|
||||||
|
if (rv != null)
|
||||||
|
return rv;
|
||||||
|
if (debug)
|
||||||
|
_log.debug("Ratchet tag not found after PQ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferRatchet) {
|
||||||
|
// Ratchet DH
|
||||||
|
rv = _context.eciesEngine().decryptSlow(data, ecKey, keyManager.getECSKM());
|
||||||
|
boolean ok = rv != null;
|
||||||
|
keyManager.reportDecryptResult(true, ok);
|
||||||
|
if (ok)
|
||||||
|
return rv;
|
||||||
|
if (debug)
|
||||||
|
_log.debug("Ratchet NS decrypt failed before PQ");
|
||||||
|
}
|
||||||
|
|
||||||
|
// PQ DH
|
||||||
|
// Minimum size checks for the larger New Session message are in ECIESAEADEngine.x_decryptSlow().
|
||||||
|
rv = _context.eciesEngine().decryptSlow(data, pqKey, keyManager.getPQSKM());
|
||||||
|
boolean isok = rv != null;
|
||||||
|
keyManager.reportDecryptResult(false, isok);
|
||||||
|
if (isok)
|
||||||
|
return rv;
|
||||||
|
if (debug)
|
||||||
|
_log.debug("PQ NS decrypt failed");
|
||||||
|
|
||||||
|
if (!preferRatchet) {
|
||||||
|
// Ratchet DH
|
||||||
|
rv = _context.eciesEngine().decryptSlow(data, ecKey, keyManager.getECSKM());
|
||||||
|
boolean ok = rv != null;
|
||||||
|
keyManager.reportDecryptResult(true, ok);
|
||||||
|
if (!ok && debug)
|
||||||
|
_log.debug("Ratchet NS decrypt failed after PQ");
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
}
|
231
router/java/src/net/i2p/router/crypto/ratchet/MuxedPQSKM.java
Normal file
231
router/java/src/net/i2p/router/crypto/ratchet/MuxedPQSKM.java
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
package net.i2p.router.crypto.ratchet;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.crypto.EncType;
|
||||||
|
import net.i2p.crypto.TagSetHandle;
|
||||||
|
import net.i2p.crypto.SessionKeyManager;
|
||||||
|
import net.i2p.data.PublicKey;
|
||||||
|
import net.i2p.data.SessionKey;
|
||||||
|
import net.i2p.data.SessionTag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Both EC and PQ
|
||||||
|
*
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
public class MuxedPQSKM extends SessionKeyManager {
|
||||||
|
|
||||||
|
private final RatchetSKM _ec;
|
||||||
|
private final RatchetSKM _pq;
|
||||||
|
private final AtomicInteger _ecCounter = new AtomicInteger();
|
||||||
|
private final AtomicInteger _pqCounter = new AtomicInteger();
|
||||||
|
// PQ is about this much slower than EC
|
||||||
|
private static final int PQ_SLOW_FACTOR = 2;
|
||||||
|
private static final int RESTART_COUNTERS = 500;
|
||||||
|
|
||||||
|
public MuxedPQSKM(RatchetSKM ec, RatchetSKM pq) {
|
||||||
|
_ec = ec;
|
||||||
|
_pq = pq;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RatchetSKM getECSKM() { return _ec; }
|
||||||
|
|
||||||
|
public RatchetSKM getPQSKM() { return _pq; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should we try the Ratchet slow decrypt before PQ slow decrypt?
|
||||||
|
* Adaptive test based on previous mix of traffic for this SKM,
|
||||||
|
* as reported by reportDecryptResult().
|
||||||
|
*/
|
||||||
|
boolean preferRatchet() {
|
||||||
|
int ec = _ecCounter.get();
|
||||||
|
int pq = _pqCounter.get();
|
||||||
|
if (ec > RESTART_COUNTERS / 10 &&
|
||||||
|
pq > RESTART_COUNTERS / 10 &&
|
||||||
|
ec + pq > RESTART_COUNTERS) {
|
||||||
|
_ecCounter.set(0);
|
||||||
|
_pqCounter.set(0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return ec >= pq / PQ_SLOW_FACTOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report the result of a slow decrypt attempt.
|
||||||
|
*
|
||||||
|
* @param isRatchet true for EC, false for PQ
|
||||||
|
* @param success true for successful decrypt
|
||||||
|
*/
|
||||||
|
void reportDecryptResult(boolean isRatchet, boolean success) {
|
||||||
|
if (success) {
|
||||||
|
if (isRatchet)
|
||||||
|
_ecCounter.incrementAndGet();
|
||||||
|
else
|
||||||
|
_pqCounter.incrementAndGet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ElG only
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public SessionKey getCurrentKey(PublicKey target) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ElG only
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public SessionKey getCurrentOrNewKey(PublicKey target) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ElG only
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void createSession(PublicKey target, SessionKey key) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ElG only
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public SessionKey createSession(PublicKey target) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ElG only
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public SessionTag consumeNextAvailableTag(PublicKey target, SessionKey key) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EC/PQ
|
||||||
|
*/
|
||||||
|
public RatchetEntry consumeNextAvailableTag(PublicKey target) {
|
||||||
|
EncType type = target.getType();
|
||||||
|
if (type == EncType.ECIES_X25519)
|
||||||
|
return _ec.consumeNextAvailableTag(target);
|
||||||
|
else
|
||||||
|
return _pq.consumeNextAvailableTag(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTagsToSend() { return 0; };
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLowThreshold() { return 0; };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ElG only
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean shouldSendTags(PublicKey target, SessionKey key) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ElG only
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean shouldSendTags(PublicKey target, SessionKey key, int lowThreshold) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAvailableTags(PublicKey target, SessionKey key) {
|
||||||
|
EncType type = target.getType();
|
||||||
|
if (type == EncType.ECIES_X25519)
|
||||||
|
return _ec.getAvailableTags(target, key);
|
||||||
|
else
|
||||||
|
return _pq.getAvailableTags(target, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getAvailableTimeLeft(PublicKey target, SessionKey key) {
|
||||||
|
EncType type = target.getType();
|
||||||
|
if (type == EncType.ECIES_X25519)
|
||||||
|
return _ec.getAvailableTimeLeft(target, key);
|
||||||
|
else
|
||||||
|
return _pq.getAvailableTimeLeft(target, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ElG only
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public TagSetHandle tagsDelivered(PublicKey target, SessionKey key, Set<SessionTag> sessionTags) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ElG only
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void tagsReceived(SessionKey key, Set<SessionTag> sessionTags) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ElG only
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void tagsReceived(SessionKey key, Set<SessionTag> sessionTags, long expire) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EC only.
|
||||||
|
* One time session
|
||||||
|
* We do not support PQ one-time sessions on MuxedPQSKM.
|
||||||
|
*
|
||||||
|
* @param expire time from now
|
||||||
|
*/
|
||||||
|
public void tagsReceived(SessionKey key, RatchetSessionTag tag, long expire) {
|
||||||
|
_ec.tagsReceived(key, tag, expire);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SessionKey consumeTag(SessionTag tag) {
|
||||||
|
RatchetSessionTag rstag = new RatchetSessionTag(tag.getData());
|
||||||
|
SessionKey rv = _ec.consumeTag(rstag);
|
||||||
|
if (rv == null) {
|
||||||
|
rv = _pq.consumeTag(rstag);
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
_ec.shutdown();
|
||||||
|
_pq.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void renderStatusHTML(Writer out) throws IOException {
|
||||||
|
_ec.renderStatusHTML(out);
|
||||||
|
_pq.renderStatusHTML(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ElG only
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void failTags(PublicKey target, SessionKey key, TagSetHandle ts) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ElG only
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void tagsAcked(PublicKey target, SessionKey key, TagSetHandle ts) {
|
||||||
|
}
|
||||||
|
}
|
@ -54,6 +54,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
|
|||||||
private final HKDF _hkdf;
|
private final HKDF _hkdf;
|
||||||
private final DecayingHashSet _replayFilter;
|
private final DecayingHashSet _replayFilter;
|
||||||
private final Destination _destination;
|
private final Destination _destination;
|
||||||
|
private final EncType _type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Let outbound session tags sit around for this long before expiring them.
|
* Let outbound session tags sit around for this long before expiring them.
|
||||||
@ -83,7 +84,17 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
|
|||||||
* @since 0.9.48
|
* @since 0.9.48
|
||||||
*/
|
*/
|
||||||
public RatchetSKM(RouterContext context) {
|
public RatchetSKM(RouterContext context) {
|
||||||
this(context, null);
|
this(context, null, EncType.ECIES_X25519);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ECIES only.
|
||||||
|
*
|
||||||
|
* @param dest null for router's SKM only
|
||||||
|
* @since 0.9.48
|
||||||
|
*/
|
||||||
|
public RatchetSKM(RouterContext context, Destination dest) {
|
||||||
|
this(context, dest, EncType.ECIES_X25519);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -91,12 +102,15 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
|
|||||||
* client manager.
|
* client manager.
|
||||||
*
|
*
|
||||||
* @param dest null for router's SKM only
|
* @param dest null for router's SKM only
|
||||||
|
* @param type the encryption type
|
||||||
|
* @since 0.9.67
|
||||||
*/
|
*/
|
||||||
public RatchetSKM(RouterContext context, Destination dest) {
|
public RatchetSKM(RouterContext context, Destination dest, EncType type) {
|
||||||
super(context);
|
super(context);
|
||||||
_log = context.logManager().getLog(RatchetSKM.class);
|
_log = context.logManager().getLog(RatchetSKM.class);
|
||||||
_context = context;
|
_context = context;
|
||||||
_destination = dest;
|
_destination = dest;
|
||||||
|
_type = type;
|
||||||
_outboundSessions = new ConcurrentHashMap<PublicKey, OutboundSession>(64);
|
_outboundSessions = new ConcurrentHashMap<PublicKey, OutboundSession>(64);
|
||||||
_pendingOutboundSessions = new HashMap<PublicKey, List<OutboundSession>>(64);
|
_pendingOutboundSessions = new HashMap<PublicKey, List<OutboundSession>>(64);
|
||||||
_inboundTagSets = new ConcurrentHashMap<RatchetSessionTag, RatchetTagSet>(128);
|
_inboundTagSets = new ConcurrentHashMap<RatchetSessionTag, RatchetTagSet>(128);
|
||||||
@ -146,6 +160,15 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
|
|||||||
return _destination;
|
return _destination;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EncType for this SKM
|
||||||
|
*
|
||||||
|
* @since 0.9.67
|
||||||
|
*/
|
||||||
|
public EncType getType() {
|
||||||
|
return _type;
|
||||||
|
}
|
||||||
|
|
||||||
/** RatchetTagSet */
|
/** RatchetTagSet */
|
||||||
private Set<RatchetTagSet> getRatchetTagSets() {
|
private Set<RatchetTagSet> getRatchetTagSets() {
|
||||||
synchronized (_inboundTagSets) {
|
synchronized (_inboundTagSets) {
|
||||||
@ -201,8 +224,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
|
|||||||
*/
|
*/
|
||||||
boolean createSession(PublicKey target, Destination d, HandshakeState state, ReplyCallback callback) {
|
boolean createSession(PublicKey target, Destination d, HandshakeState state, ReplyCallback callback) {
|
||||||
EncType type = target.getType();
|
EncType type = target.getType();
|
||||||
if (type != EncType.ECIES_X25519)
|
if (type != _type)
|
||||||
throw new IllegalArgumentException("Bad public key type " + type);
|
throw new IllegalArgumentException("Bad public key type " + type + " expected " + _type);
|
||||||
OutboundSession sess = new OutboundSession(target, d, null, state, callback);
|
OutboundSession sess = new OutboundSession(target, d, null, state, callback);
|
||||||
boolean isInbound = state.getRole() == HandshakeState.RESPONDER;
|
boolean isInbound = state.getRole() == HandshakeState.RESPONDER;
|
||||||
if (isInbound) {
|
if (isInbound) {
|
||||||
@ -247,8 +270,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
|
|||||||
boolean updateSession(PublicKey target, HandshakeState oldState, HandshakeState state,
|
boolean updateSession(PublicKey target, HandshakeState oldState, HandshakeState state,
|
||||||
ReplyCallback callback, SplitKeys split) {
|
ReplyCallback callback, SplitKeys split) {
|
||||||
EncType type = target.getType();
|
EncType type = target.getType();
|
||||||
if (type != EncType.ECIES_X25519)
|
if (type != _type)
|
||||||
throw new IllegalArgumentException("Bad public key type " + type);
|
throw new IllegalArgumentException("Bad public key type " + type + " expected " + _type);
|
||||||
boolean isInbound = state.getRole() == HandshakeState.RESPONDER;
|
boolean isInbound = state.getRole() == HandshakeState.RESPONDER;
|
||||||
if (isInbound) {
|
if (isInbound) {
|
||||||
// we are Bob, NSR sent
|
// we are Bob, NSR sent
|
||||||
|
@ -32,6 +32,7 @@ import net.i2p.data.router.RouterIdentity;
|
|||||||
import net.i2p.data.router.RouterInfo;
|
import net.i2p.data.router.RouterInfo;
|
||||||
import net.i2p.router.LeaseSetKeys;
|
import net.i2p.router.LeaseSetKeys;
|
||||||
import net.i2p.router.RouterContext;
|
import net.i2p.router.RouterContext;
|
||||||
|
import net.i2p.router.crypto.ratchet.MuxedPQSKM;
|
||||||
import net.i2p.router.crypto.ratchet.MuxedSKM;
|
import net.i2p.router.crypto.ratchet.MuxedSKM;
|
||||||
import net.i2p.router.crypto.ratchet.RatchetSKM;
|
import net.i2p.router.crypto.ratchet.RatchetSKM;
|
||||||
import net.i2p.router.crypto.ratchet.RatchetSessionTag;
|
import net.i2p.router.crypto.ratchet.RatchetSessionTag;
|
||||||
@ -46,13 +47,13 @@ import net.i2p.util.Log;
|
|||||||
public class GarlicMessageBuilder {
|
public class GarlicMessageBuilder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ELGAMAL_2048 only.
|
* ELGAMAL_2048 only; returns false for others
|
||||||
*
|
*
|
||||||
* @param local non-null; do not use this method for the router's SessionKeyManager
|
* @param local non-null; do not use this method for the router's SessionKeyManager
|
||||||
* @param minTagOverride 0 for no override, > 0 to override SKM's settings
|
* @param minTagOverride 0 for no override, > 0 to override SKM's settings
|
||||||
*/
|
*/
|
||||||
static boolean needsTags(RouterContext ctx, PublicKey key, Hash local, int minTagOverride) {
|
static boolean needsTags(RouterContext ctx, PublicKey key, Hash local, int minTagOverride) {
|
||||||
if (key.getType() == EncType.ECIES_X25519)
|
if (LeaseSetKeys.SET_EC_PQ_ALL.contains(key.getType()))
|
||||||
return false;
|
return false;
|
||||||
SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(local);
|
SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(local);
|
||||||
if (skm == null)
|
if (skm == null)
|
||||||
@ -275,7 +276,7 @@ public class GarlicMessageBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ECIES_X25519 only.
|
* ECIES_X25519 and PQ only.
|
||||||
* Called by OCMJH only.
|
* Called by OCMJH only.
|
||||||
*
|
*
|
||||||
* @param ctx scope
|
* @param ctx scope
|
||||||
@ -289,7 +290,8 @@ public class GarlicMessageBuilder {
|
|||||||
Hash from, Destination to, SessionKeyManager skm,
|
Hash from, Destination to, SessionKeyManager skm,
|
||||||
ReplyCallback callback) {
|
ReplyCallback callback) {
|
||||||
PublicKey key = config.getRecipientPublicKey();
|
PublicKey key = config.getRecipientPublicKey();
|
||||||
if (key.getType() != EncType.ECIES_X25519)
|
EncType type = key.getType();
|
||||||
|
if (!LeaseSetKeys.SET_EC_PQ_ALL.contains(type))
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
Log log = ctx.logManager().getLog(GarlicMessageBuilder.class);
|
Log log = ctx.logManager().getLog(GarlicMessageBuilder.class);
|
||||||
GarlicMessage msg = new GarlicMessage(ctx);
|
GarlicMessage msg = new GarlicMessage(ctx);
|
||||||
@ -300,7 +302,7 @@ public class GarlicMessageBuilder {
|
|||||||
log.warn("No LSK for " + from.toBase32());
|
log.warn("No LSK for " + from.toBase32());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
PrivateKey priv = lsk.getDecryptionKey(EncType.ECIES_X25519);
|
PrivateKey priv = lsk.getDecryptionKey(type);
|
||||||
if (priv == null) {
|
if (priv == null) {
|
||||||
if (log.shouldWarn())
|
if (log.shouldWarn())
|
||||||
log.warn("No key for " + from.toBase32());
|
log.warn("No key for " + from.toBase32());
|
||||||
@ -312,6 +314,9 @@ public class GarlicMessageBuilder {
|
|||||||
rskm = (RatchetSKM) skm;
|
rskm = (RatchetSKM) skm;
|
||||||
} else if (skm instanceof MuxedSKM) {
|
} else if (skm instanceof MuxedSKM) {
|
||||||
rskm = ((MuxedSKM) skm).getECSKM();
|
rskm = ((MuxedSKM) skm).getECSKM();
|
||||||
|
} else if (skm instanceof MuxedPQSKM) {
|
||||||
|
MuxedPQSKM mskm = (MuxedPQSKM) skm;
|
||||||
|
rskm = type.isPQ() ? mskm.getPQSKM() : mskm.getECSKM();
|
||||||
} else {
|
} else {
|
||||||
if (log.shouldWarn())
|
if (log.shouldWarn())
|
||||||
log.warn("No SKM for " + from.toBase32());
|
log.warn("No SKM for " + from.toBase32());
|
||||||
@ -338,7 +343,7 @@ public class GarlicMessageBuilder {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt from an anonymous source.
|
* Encrypt from an anonymous source.
|
||||||
* ECIES_X25519 only.
|
* ECIES_X25519 only. PQ not supported.
|
||||||
* Called by MessageWrapper only.
|
* Called by MessageWrapper only.
|
||||||
*
|
*
|
||||||
* @param ctx scope
|
* @param ctx scope
|
||||||
@ -348,7 +353,7 @@ public class GarlicMessageBuilder {
|
|||||||
*/
|
*/
|
||||||
public static GarlicMessage buildECIESMessage(RouterContext ctx, GarlicConfig config) {
|
public static GarlicMessage buildECIESMessage(RouterContext ctx, GarlicConfig config) {
|
||||||
PublicKey key = config.getRecipientPublicKey();
|
PublicKey key = config.getRecipientPublicKey();
|
||||||
if (key.getType() != EncType.ECIES_X25519)
|
if (!LeaseSetKeys.SET_EC_PQ_ALL.contains(key.getType()))
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
Log log = ctx.logManager().getLog(GarlicMessageBuilder.class);
|
Log log = ctx.logManager().getLog(GarlicMessageBuilder.class);
|
||||||
GarlicMessage msg = new GarlicMessage(ctx);
|
GarlicMessage msg = new GarlicMessage(ctx);
|
||||||
|
@ -19,6 +19,7 @@ import net.i2p.data.PrivateKey;
|
|||||||
import net.i2p.data.i2np.GarlicClove;
|
import net.i2p.data.i2np.GarlicClove;
|
||||||
import net.i2p.data.i2np.GarlicMessage;
|
import net.i2p.data.i2np.GarlicMessage;
|
||||||
import net.i2p.router.RouterContext;
|
import net.i2p.router.RouterContext;
|
||||||
|
import net.i2p.router.crypto.ratchet.MuxedPQSKM;
|
||||||
import net.i2p.router.crypto.ratchet.MuxedSKM;
|
import net.i2p.router.crypto.ratchet.MuxedSKM;
|
||||||
import net.i2p.router.crypto.ratchet.RatchetSKM;
|
import net.i2p.router.crypto.ratchet.RatchetSKM;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
@ -44,7 +45,7 @@ public class GarlicMessageParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supports both ELGAMAL_2048 and ECIES_X25519.
|
* Supports ELGAMAL_2048, ECIES_X25519, and PQ
|
||||||
*
|
*
|
||||||
* @param encryptionKey either type
|
* @param encryptionKey either type
|
||||||
* @param skm use tags from this session key manager
|
* @param skm use tags from this session key manager
|
||||||
@ -65,6 +66,8 @@ public class GarlicMessageParser {
|
|||||||
rskm = (RatchetSKM) skm;
|
rskm = (RatchetSKM) skm;
|
||||||
} else if (skm instanceof MuxedSKM) {
|
} else if (skm instanceof MuxedSKM) {
|
||||||
rskm = ((MuxedSKM) skm).getECSKM();
|
rskm = ((MuxedSKM) skm).getECSKM();
|
||||||
|
} else if (skm instanceof MuxedPQSKM) {
|
||||||
|
rskm = ((MuxedPQSKM) skm).getECSKM();
|
||||||
} else {
|
} else {
|
||||||
if (_log.shouldWarn())
|
if (_log.shouldWarn())
|
||||||
_log.warn("No SKM to decrypt ECIES");
|
_log.warn("No SKM to decrypt ECIES");
|
||||||
@ -80,6 +83,27 @@ public class GarlicMessageParser {
|
|||||||
_log.warn("ECIES decrypt fail");
|
_log.warn("ECIES decrypt fail");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
} else if (type.isPQ()) {
|
||||||
|
RatchetSKM rskm;
|
||||||
|
if (skm instanceof RatchetSKM) {
|
||||||
|
rskm = (RatchetSKM) skm;
|
||||||
|
} else if (skm instanceof MuxedPQSKM) {
|
||||||
|
rskm = ((MuxedPQSKM) skm).getPQSKM();
|
||||||
|
} else {
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("No SKM to decrypt PQ");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
CloveSet rv = _context.eciesEngine().decrypt(encData, encryptionKey, rskm);
|
||||||
|
if (rv != null) {
|
||||||
|
if (_log.shouldDebug())
|
||||||
|
_log.debug("PQ decrypt success, cloves: " + rv.getCloveCount());
|
||||||
|
return rv;
|
||||||
|
} else {
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("PQ decrypt fail");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (_log.shouldWarn())
|
if (_log.shouldWarn())
|
||||||
_log.warn("Can't decrypt with key type " + type);
|
_log.warn("Can't decrypt with key type " + type);
|
||||||
@ -112,7 +136,7 @@ public class GarlicMessageParser {
|
|||||||
/**
|
/**
|
||||||
* Supports both ELGAMAL_2048 and ECIES_X25519.
|
* Supports both ELGAMAL_2048 and ECIES_X25519.
|
||||||
*
|
*
|
||||||
* @param elgKey must be ElG, non-null
|
* @param elgKey must be ElG OR PQ, non-null
|
||||||
* @param ecKey must be EC, non-null
|
* @param ecKey must be EC, non-null
|
||||||
* @param skm use tags from this session key manager
|
* @param skm use tags from this session key manager
|
||||||
* @return null on error
|
* @return null on error
|
||||||
@ -125,6 +149,10 @@ public class GarlicMessageParser {
|
|||||||
if (skm instanceof MuxedSKM) {
|
if (skm instanceof MuxedSKM) {
|
||||||
MuxedSKM mskm = (MuxedSKM) skm;
|
MuxedSKM mskm = (MuxedSKM) skm;
|
||||||
rv = _context.eciesEngine().decrypt(encData, elgKey, ecKey, mskm);
|
rv = _context.eciesEngine().decrypt(encData, elgKey, ecKey, mskm);
|
||||||
|
} else if (skm instanceof MuxedPQSKM) {
|
||||||
|
MuxedPQSKM mskm = (MuxedPQSKM) skm;
|
||||||
|
// EC is first
|
||||||
|
rv = _context.eciesEngine().decrypt(encData, ecKey, elgKey, mskm);
|
||||||
} else if (skm instanceof RatchetSKM) {
|
} else if (skm instanceof RatchetSKM) {
|
||||||
// unlikely, if we have two keys we should have a MuxedSKM
|
// unlikely, if we have two keys we should have a MuxedSKM
|
||||||
RatchetSKM rskm = (RatchetSKM) skm;
|
RatchetSKM rskm = (RatchetSKM) skm;
|
||||||
|
@ -65,16 +65,25 @@ public class GarlicMessageReceiver {
|
|||||||
if (keys != null && skm != null) {
|
if (keys != null && skm != null) {
|
||||||
decryptionKey = keys.getDecryptionKey();
|
decryptionKey = keys.getDecryptionKey();
|
||||||
decryptionKey2 = keys.getDecryptionKey(EncType.ECIES_X25519);
|
decryptionKey2 = keys.getDecryptionKey(EncType.ECIES_X25519);
|
||||||
if (decryptionKey == null && decryptionKey2 == null) {
|
// this will return any of the PQ types
|
||||||
|
PrivateKey decryptionKey3 = keys.getPQDecryptionKey();
|
||||||
|
if (decryptionKey == null && decryptionKey2 == null && decryptionKey3 == null) {
|
||||||
if (_log.shouldWarn())
|
if (_log.shouldWarn())
|
||||||
_log.warn("No key to decrypt for " + _clientDestination.toBase32());
|
_log.warn("No key to decrypt for " + _clientDestination.toBase32());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// ElG + PQ disallowed
|
||||||
if (decryptionKey == null) {
|
if (decryptionKey == null) {
|
||||||
// swap
|
// swap
|
||||||
|
if (decryptionKey3 != null) {
|
||||||
|
// PQ first if present
|
||||||
|
decryptionKey = decryptionKey3;
|
||||||
|
} else {
|
||||||
|
// EC only
|
||||||
decryptionKey = decryptionKey2;
|
decryptionKey = decryptionKey2;
|
||||||
decryptionKey2 = null;
|
decryptionKey2 = null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (_log.shouldLog(Log.WARN))
|
if (_log.shouldLog(Log.WARN))
|
||||||
_log.warn("Not decrypting " + message + " for disconnected " + _clientDestination.toBase32());
|
_log.warn("Not decrypting " + message + " for disconnected " + _clientDestination.toBase32());
|
||||||
|
@ -124,7 +124,7 @@ class OutboundClientMessageJobHelper {
|
|||||||
SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(from);
|
SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(from);
|
||||||
if (skm == null)
|
if (skm == null)
|
||||||
return null;
|
return null;
|
||||||
boolean isECIES = recipientPK.getType() == EncType.ECIES_X25519;
|
boolean isECIES = recipientPK.getType() != EncType.ELGAMAL_2048;
|
||||||
// force ack off if ECIES
|
// force ack off if ECIES
|
||||||
boolean ackInGarlic = isECIES ? false : requireAck;
|
boolean ackInGarlic = isECIES ? false : requireAck;
|
||||||
GarlicConfig config = createGarlicConfig(ctx, replyToken, expiration, recipientPK, dataClove,
|
GarlicConfig config = createGarlicConfig(ctx, replyToken, expiration, recipientPK, dataClove,
|
||||||
|
@ -359,7 +359,7 @@ public class IterativeSearchJob extends FloodSearchJob {
|
|||||||
}
|
}
|
||||||
LeaseSetKeys lsk = ctx.keyManager().getKeys(_fromLocalDest);
|
LeaseSetKeys lsk = ctx.keyManager().getKeys(_fromLocalDest);
|
||||||
supportsRatchet = lsk != null &&
|
supportsRatchet = lsk != null &&
|
||||||
lsk.isSupported(EncType.ECIES_X25519) &&
|
(lsk.isSupported(EncType.ECIES_X25519) || lsk.getPQDecryptionKey() != null) &&
|
||||||
DatabaseLookupMessage.supportsRatchetReplies(ri);
|
DatabaseLookupMessage.supportsRatchetReplies(ri);
|
||||||
supportsElGamal = !supportsRatchet &&
|
supportsElGamal = !supportsRatchet &&
|
||||||
lsk != null &&
|
lsk != null &&
|
||||||
|
@ -17,6 +17,7 @@ import net.i2p.data.i2np.I2NPMessage;
|
|||||||
import net.i2p.data.router.RouterInfo;
|
import net.i2p.data.router.RouterInfo;
|
||||||
import net.i2p.router.RouterContext;
|
import net.i2p.router.RouterContext;
|
||||||
import net.i2p.router.crypto.TransientSessionKeyManager;
|
import net.i2p.router.crypto.TransientSessionKeyManager;
|
||||||
|
import net.i2p.router.crypto.ratchet.MuxedPQSKM;
|
||||||
import net.i2p.router.crypto.ratchet.MuxedSKM;
|
import net.i2p.router.crypto.ratchet.MuxedSKM;
|
||||||
import net.i2p.router.crypto.ratchet.RatchetSKM;
|
import net.i2p.router.crypto.ratchet.RatchetSKM;
|
||||||
import net.i2p.router.crypto.ratchet.RatchetSessionTag;
|
import net.i2p.router.crypto.ratchet.RatchetSessionTag;
|
||||||
@ -242,6 +243,8 @@ public class MessageWrapper {
|
|||||||
rskm = (RatchetSKM) skm;
|
rskm = (RatchetSKM) skm;
|
||||||
} else if (skm instanceof MuxedSKM) {
|
} else if (skm instanceof MuxedSKM) {
|
||||||
rskm = ((MuxedSKM) skm).getECSKM();
|
rskm = ((MuxedSKM) skm).getECSKM();
|
||||||
|
} else if (skm instanceof MuxedPQSKM) {
|
||||||
|
rskm = ((MuxedPQSKM) skm).getECSKM();
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("skm not a ratchet " + skm);
|
throw new IllegalStateException("skm not a ratchet " + skm);
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import net.i2p.router.TunnelInfo;
|
|||||||
import net.i2p.router.TunnelManagerFacade;
|
import net.i2p.router.TunnelManagerFacade;
|
||||||
import net.i2p.router.TunnelPoolSettings;
|
import net.i2p.router.TunnelPoolSettings;
|
||||||
import net.i2p.router.crypto.ratchet.RatchetSKM;
|
import net.i2p.router.crypto.ratchet.RatchetSKM;
|
||||||
|
import net.i2p.router.crypto.ratchet.MuxedPQSKM;
|
||||||
import net.i2p.router.crypto.ratchet.MuxedSKM;
|
import net.i2p.router.crypto.ratchet.MuxedSKM;
|
||||||
import net.i2p.router.networkdb.kademlia.MessageWrapper;
|
import net.i2p.router.networkdb.kademlia.MessageWrapper;
|
||||||
import net.i2p.router.networkdb.kademlia.MessageWrapper.OneTimeSession;
|
import net.i2p.router.networkdb.kademlia.MessageWrapper.OneTimeSession;
|
||||||
@ -318,6 +319,9 @@ abstract class BuildRequestor {
|
|||||||
} else if (replySKM instanceof MuxedSKM) {
|
} else if (replySKM instanceof MuxedSKM) {
|
||||||
MuxedSKM mskm = (MuxedSKM) replySKM;
|
MuxedSKM mskm = (MuxedSKM) replySKM;
|
||||||
mskm.tagsReceived(ots.key, ots.rtag, 2 * BUILD_MSG_TIMEOUT);
|
mskm.tagsReceived(ots.key, ots.rtag, 2 * BUILD_MSG_TIMEOUT);
|
||||||
|
} else if (replySKM instanceof MuxedPQSKM) {
|
||||||
|
MuxedPQSKM mskm = (MuxedPQSKM) replySKM;
|
||||||
|
mskm.tagsReceived(ots.key, ots.rtag, 2 * BUILD_MSG_TIMEOUT);
|
||||||
} else {
|
} else {
|
||||||
// non-EC client, shouldn't happen, checked at top of createTunnelBuildMessage() below
|
// non-EC client, shouldn't happen, checked at top of createTunnelBuildMessage() below
|
||||||
if (log.shouldWarn())
|
if (log.shouldWarn())
|
||||||
|
@ -13,6 +13,7 @@ import net.i2p.router.OutNetMessage;
|
|||||||
import net.i2p.router.ReplyJob;
|
import net.i2p.router.ReplyJob;
|
||||||
import net.i2p.router.RouterContext;
|
import net.i2p.router.RouterContext;
|
||||||
import net.i2p.router.TunnelInfo;
|
import net.i2p.router.TunnelInfo;
|
||||||
|
import net.i2p.router.crypto.ratchet.MuxedPQSKM;
|
||||||
import net.i2p.router.crypto.ratchet.MuxedSKM;
|
import net.i2p.router.crypto.ratchet.MuxedSKM;
|
||||||
import net.i2p.router.crypto.ratchet.RatchetSessionTag;
|
import net.i2p.router.crypto.ratchet.RatchetSessionTag;
|
||||||
import net.i2p.router.crypto.ratchet.RatchetSKM;
|
import net.i2p.router.crypto.ratchet.RatchetSKM;
|
||||||
@ -368,6 +369,8 @@ class TestJob extends JobImpl {
|
|||||||
rskm = (RatchetSKM) skm;
|
rskm = (RatchetSKM) skm;
|
||||||
} else if (skm instanceof MuxedSKM) {
|
} else if (skm instanceof MuxedSKM) {
|
||||||
rskm = ((MuxedSKM) skm).getECSKM();
|
rskm = ((MuxedSKM) skm).getECSKM();
|
||||||
|
} else if (skm instanceof MuxedPQSKM) {
|
||||||
|
rskm = ((MuxedPQSKM) skm).getECSKM();
|
||||||
} else {
|
} else {
|
||||||
// shouldn't happen
|
// shouldn't happen
|
||||||
rskm = null;
|
rskm = null;
|
||||||
|
Reference in New Issue
Block a user