forked from I2P_Developers/i2p.i2p
I2P changes for ratchet
Some checks failed
Java CI / build (push) Has been cancelled
Java CI / javadoc-latest (push) Has been cancelled
Java CI / build-java7 (push) Has been cancelled
Java with IzPack Snapshot Setup / setup (push) Has been cancelled
Sync Primary Repository to GitHub Mirror / sync (push) Has been cancelled
Some checks failed
Java CI / build (push) Has been cancelled
Java CI / javadoc-latest (push) Has been cancelled
Java CI / build-java7 (push) Has been cancelled
Java with IzPack Snapshot Setup / setup (push) Has been cancelled
Sync Primary Repository to GitHub Mirror / sync (push) Has been cancelled
This commit is contained in:
@ -128,6 +128,10 @@ Public domain except as listed below:
|
||||
Copyright (C) 2006 The Android Open Source Project
|
||||
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:
|
||||
(not included in distribution packages)
|
||||
|
@ -604,6 +604,9 @@
|
||||
<%
|
||||
boolean has0 = editBean.hasEncType(curTunnel, 0);
|
||||
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>
|
||||
<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)%> >
|
||||
ECIES-X25519</option>
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -676,6 +676,9 @@
|
||||
<%
|
||||
boolean has0 = editBean.hasEncType(curTunnel, 0);
|
||||
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\" ";
|
||||
%>
|
||||
<tr>
|
||||
@ -690,7 +693,25 @@
|
||||
<option title="<%=intl._t("This is the default, recommended option")%>" value="4" <%=((has4 && !has0) ? " selected=\"selected\"" : edisabled)%> >
|
||||
ECIES-X25519</option>
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -887,7 +887,7 @@
|
||||
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="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.*" />
|
||||
<!-- apps and bridges starting here, alphabetical please -->
|
||||
<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"),
|
||||
|
||||
/** @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;
|
||||
|
||||
|
@ -52,14 +52,83 @@ public enum EncType {
|
||||
* Pubkey 32 bytes; privkey 32 bytes
|
||||
* @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 EncAlgo base;
|
||||
private final String algoName, since;
|
||||
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,
|
||||
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");
|
||||
code = cod;
|
||||
pubkeyLen = pubLen;
|
||||
@ -78,6 +147,7 @@ public enum EncType {
|
||||
params = pSpec;
|
||||
since = supportedSince;
|
||||
isAvail = x_isAvailable();
|
||||
isPQ = base == EncAlgo.ECIES_MLKEM;
|
||||
}
|
||||
|
||||
/** the unique identifier for this type */
|
||||
@ -120,11 +190,16 @@ public enum EncType {
|
||||
}
|
||||
|
||||
private boolean x_isAvailable() {
|
||||
if (ELGAMAL_2048 == this)
|
||||
return true;
|
||||
// EC types are placeholders for now
|
||||
if (base == EncAlgo.EC)
|
||||
return false;
|
||||
switch (base) {
|
||||
case ELGAMAL:
|
||||
return true;
|
||||
|
||||
// EC types are placeholders for now
|
||||
case EC:
|
||||
// internal types
|
||||
case ECIES_MLKEM_INT:
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
getParams();
|
||||
} catch (InvalidParameterSpecException e) {
|
||||
@ -154,6 +229,14 @@ public enum EncType {
|
||||
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;
|
||||
|
||||
static {
|
||||
|
@ -196,6 +196,9 @@ public final class KeyGenerator {
|
||||
break;
|
||||
|
||||
case ECIES_X25519:
|
||||
case MLKEM512_X25519:
|
||||
case MLKEM768_X25519:
|
||||
case MLKEM1024_X25519:
|
||||
byte[] bpriv = new byte[32];
|
||||
do {
|
||||
_context.random().nextBytes(bpriv);
|
||||
@ -238,6 +241,9 @@ public final class KeyGenerator {
|
||||
break;
|
||||
|
||||
case ECIES_X25519:
|
||||
case MLKEM512_X25519:
|
||||
case MLKEM768_X25519:
|
||||
case MLKEM1024_X25519:
|
||||
data = new byte[32];
|
||||
Curve25519.eval(data, 0, priv.getData(), null);
|
||||
break;
|
||||
@ -342,6 +348,7 @@ public final class KeyGenerator {
|
||||
}
|
||||
java.security.PublicKey pubkey = kp.getPublic();
|
||||
java.security.PrivateKey privkey = kp.getPrivate();
|
||||
|
||||
SimpleDataStructure[] keys = new SimpleDataStructure[2];
|
||||
keys[0] = SigUtil.fromJavaKey(pubkey, type);
|
||||
keys[1] = SigUtil.fromJavaKey(privkey, type);
|
||||
@ -476,6 +483,7 @@ public final class KeyGenerator {
|
||||
System.out.println(type + " private-to-public test PASSED");
|
||||
else
|
||||
System.out.println(type + " private-to-public test FAILED");
|
||||
|
||||
//System.out.println("privkey " + keys[1]);
|
||||
MessageDigest md = type.getDigestInstance();
|
||||
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 DHState localKeyPair;
|
||||
private DHState localEphemeral;
|
||||
private DHState localHybrid;
|
||||
private DHState remotePublicKey;
|
||||
private DHState remoteEphemeral;
|
||||
private DHState remoteHybrid;
|
||||
private int action;
|
||||
private final int requirements;
|
||||
private int patternIndex;
|
||||
private boolean wasCloned;
|
||||
private boolean isDestroyed;
|
||||
|
||||
/**
|
||||
* 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";
|
||||
/** SSU2 */
|
||||
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 final String patternId;
|
||||
/** NTCP2 */
|
||||
@ -151,13 +162,24 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
public static final String PATTERN_ID_N_NO_RESPONSE = "N!";
|
||||
/** 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 hash;
|
||||
private final short[] pattern;
|
||||
private static final short[] PATTERN_XK;
|
||||
private static final short[] PATTERN_IK;
|
||||
private static final short[] PATTERN_N;
|
||||
private static final short[] PATTERN_IKHFS;
|
||||
|
||||
static {
|
||||
// Parse the protocol name into its components.
|
||||
@ -200,11 +222,28 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
id = components[1].substring(0, 2);
|
||||
if (!PATTERN_ID_XK.equals(id))
|
||||
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.
|
||||
* Noise protocol name is hardcoded.
|
||||
* Not for PQ Alice side.
|
||||
*
|
||||
* @param patternId XK, IK, or N
|
||||
* @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.
|
||||
*/
|
||||
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;
|
||||
if (patternId.equals(PATTERN_ID_XK))
|
||||
@ -229,6 +288,10 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
pattern = PATTERN_N;
|
||||
else if (patternId.equals(PATTERN_ID_XK_SSU2))
|
||||
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
|
||||
throw new IllegalArgumentException("Handshake pattern is not recognized");
|
||||
short flags = pattern[0];
|
||||
@ -256,10 +319,18 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
localKeyPair = new Curve25519DHState(xdh);
|
||||
if ((flags & Pattern.FLAG_LOCAL_EPHEMERAL) != 0)
|
||||
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)
|
||||
remotePublicKey = new Curve25519DHState(xdh);
|
||||
if ((flags & Pattern.FLAG_REMOTE_EPHEMERAL) != 0)
|
||||
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();
|
||||
if (o.remoteEphemeral != null)
|
||||
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;
|
||||
if (action == SPLIT || action == COMPLETE)
|
||||
throw new CloneNotSupportedException("clone after NSR");
|
||||
@ -419,6 +507,32 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
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.
|
||||
private static final byte[] emptyPrologue = new byte [0];
|
||||
|
||||
@ -472,17 +586,11 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
if (isInitiator) {
|
||||
if ((requirements & LOCAL_PREMSG) != 0)
|
||||
symmetric.mixPublicKey(localKeyPair);
|
||||
if ((requirements & FALLBACK_PREMSG) != 0) {
|
||||
symmetric.mixPublicKey(remoteEphemeral);
|
||||
}
|
||||
if ((requirements & REMOTE_PREMSG) != 0)
|
||||
symmetric.mixPublicKey(remotePublicKey);
|
||||
} else {
|
||||
if ((requirements & REMOTE_PREMSG) != 0)
|
||||
symmetric.mixPublicKey(remotePublicKey);
|
||||
if ((requirements & FALLBACK_PREMSG) != 0) {
|
||||
symmetric.mixPublicKey(localEphemeral);
|
||||
}
|
||||
if ((requirements & LOCAL_PREMSG) != 0)
|
||||
symmetric.mixPublicKey(localKeyPair);
|
||||
}
|
||||
@ -675,6 +783,54 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
}
|
||||
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:
|
||||
{
|
||||
// Unknown token code. Abort.
|
||||
@ -857,6 +1013,35 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
mixDH(localKeyPair, remotePublicKey);
|
||||
}
|
||||
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:
|
||||
{
|
||||
@ -953,17 +1138,22 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
public synchronized void destroy() {
|
||||
isDestroyed = true;
|
||||
if (symmetric != null)
|
||||
symmetric.destroy();
|
||||
if (localKeyPair != null)
|
||||
localKeyPair.destroy();
|
||||
if (localEphemeral != null)
|
||||
localEphemeral.destroy();
|
||||
if (localHybrid != null)
|
||||
localHybrid.destroy();
|
||||
if (remotePublicKey != null)
|
||||
remotePublicKey.destroy();
|
||||
if (remoteEphemeral != null)
|
||||
remoteEphemeral.destroy();
|
||||
if (remoteHybrid != null)
|
||||
remoteHybrid.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -992,11 +1182,6 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
requirements |= REMOTE_REQUIRED;
|
||||
requirements |= REMOTE_PREMSG;
|
||||
}
|
||||
if ((flags & (Pattern.FLAG_REMOTE_EPHEM_REQ |
|
||||
Pattern.FLAG_LOCAL_EPHEM_REQ)) != 0) {
|
||||
if (isFallback)
|
||||
requirements |= FALLBACK_PREMSG;
|
||||
}
|
||||
return requirements;
|
||||
}
|
||||
|
||||
@ -1022,6 +1207,8 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
*/
|
||||
@Override
|
||||
public synchronized HandshakeState clone() throws CloneNotSupportedException {
|
||||
if (isDestroyed)
|
||||
throw new IllegalStateException("destroyed");
|
||||
return new HandshakeState(this);
|
||||
}
|
||||
|
||||
@ -1085,6 +1272,34 @@ public class HandshakeState implements Destroyable, Cloneable {
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*
|
||||
@ -111,6 +136,8 @@ class Pattern {
|
||||
return noise_pattern_XK;
|
||||
else if (name.equals("IK"))
|
||||
return noise_pattern_IK;
|
||||
else if (name.equals("IKhfs"))
|
||||
return noise_pattern_IKhfs;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -41,17 +41,26 @@ class SymmetricState implements Destroyable, Cloneable {
|
||||
private static final byte[] INIT_CK_IK;
|
||||
private static final byte[] INIT_CK_N;
|
||||
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)
|
||||
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_N = 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 {
|
||||
INIT_CK_XK = initHash(HandshakeState.protocolName);
|
||||
INIT_CK_IK = initHash(HandshakeState.protocolName2);
|
||||
INIT_CK_N = initHash(HandshakeState.protocolName3);
|
||||
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 {
|
||||
MessageDigest md = Noise.createHash("SHA256");
|
||||
md.update(INIT_CK_XK, 0, 32);
|
||||
@ -62,6 +71,12 @@ class SymmetricState implements Destroyable, Cloneable {
|
||||
md.digest(INIT_HASH_N, 0, 32);
|
||||
md.update(INIT_CK_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);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
@ -136,6 +151,15 @@ class SymmetricState implements Destroyable, Cloneable {
|
||||
} else if (patternId.equals(HandshakeState.PATTERN_ID_XK_SSU2)) {
|
||||
initCK = INIT_CK_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 {
|
||||
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
|
||||
{
|
||||
// NOTE: This updates the hash, even on failure
|
||||
System.arraycopy(h, 0, prev_h, 0, h.length);
|
||||
mixHash(ciphertext, ciphertextOffset, length);
|
||||
return cipher.decryptWithAd(prev_h, ciphertext, ciphertextOffset, plaintext, plaintextOffset, length);
|
||||
|
@ -26,6 +26,7 @@ public class LeaseSetKeys {
|
||||
private final SigningPrivateKey _revocationKey;
|
||||
private final PrivateKey _decryptionKey;
|
||||
private final PrivateKey _decryptionKeyEC;
|
||||
private final PrivateKey _decryptionKeyPQ;
|
||||
|
||||
/**
|
||||
* 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));
|
||||
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
|
||||
@ -57,9 +93,15 @@ public class LeaseSetKeys {
|
||||
if (type == EncType.ELGAMAL_2048) {
|
||||
_decryptionKey = decryptionKey;
|
||||
_decryptionKeyEC = null;
|
||||
_decryptionKeyPQ = null;
|
||||
} else if (type == EncType.ECIES_X25519) {
|
||||
_decryptionKey = null;
|
||||
_decryptionKeyEC = decryptionKey;
|
||||
_decryptionKeyPQ = null;
|
||||
} else if (type.isPQ()) {
|
||||
_decryptionKey = null;
|
||||
_decryptionKeyEC =null;
|
||||
_decryptionKeyPQ = decryptionKey;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown type " + type);
|
||||
}
|
||||
@ -68,9 +110,13 @@ public class LeaseSetKeys {
|
||||
/**
|
||||
* 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 revocationKey unused, may be null
|
||||
* @param decryptionKeys non-null, non-empty
|
||||
* @throws IllegalArgumentException
|
||||
* @since 0.9.44
|
||||
*/
|
||||
public LeaseSetKeys(Destination dest, SigningPrivateKey revocationKey, List<PrivateKey> decryptionKeys) {
|
||||
@ -79,22 +125,32 @@ public class LeaseSetKeys {
|
||||
_revocationKey = revocationKey;
|
||||
PrivateKey elg = null;
|
||||
PrivateKey ec = null;
|
||||
PrivateKey pq = null;
|
||||
for (PrivateKey pk : decryptionKeys) {
|
||||
EncType type = pk.getType();
|
||||
if (type == EncType.ELGAMAL_2048) {
|
||||
if (elg != null)
|
||||
throw new IllegalArgumentException("Multiple keys same type");
|
||||
if (pq != null)
|
||||
throw new IllegalArgumentException("Invalid combination ElG + PQ");
|
||||
elg = pk;
|
||||
} else if (type == EncType.ECIES_X25519) {
|
||||
if (ec != null)
|
||||
throw new IllegalArgumentException("Multiple keys same type");
|
||||
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 {
|
||||
throw new IllegalArgumentException("Unknown type " + type);
|
||||
}
|
||||
}
|
||||
_decryptionKey = elg;
|
||||
_decryptionKeyEC = ec;
|
||||
_decryptionKeyPQ = pq;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -128,9 +184,17 @@ public class LeaseSetKeys {
|
||||
return _decryptionKey;
|
||||
if (type == EncType.ECIES_X25519)
|
||||
return _decryptionKeyEC;
|
||||
if (type.isPQ() && _decryptionKeyPQ != null && _decryptionKeyPQ.getType() == type)
|
||||
return _decryptionKeyPQ;
|
||||
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?
|
||||
*
|
||||
@ -141,6 +205,8 @@ public class LeaseSetKeys {
|
||||
return _decryptionKey != null;
|
||||
if (type == EncType.ECIES_X25519)
|
||||
return _decryptionKeyEC != null;
|
||||
if (type.isPQ())
|
||||
return _decryptionKeyPQ != null && _decryptionKeyPQ.getType() == type;
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -152,6 +218,27 @@ public class LeaseSetKeys {
|
||||
public Set<EncType> getSupportedEncryption() {
|
||||
if (_decryptionKey != null)
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.crypto.EncType;
|
||||
import net.i2p.crypto.SessionKeyManager;
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.DataHelper;
|
||||
@ -50,6 +51,7 @@ import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.crypto.TransientSessionKeyManager;
|
||||
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.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
@ -605,6 +607,8 @@ class ClientConnectionRunner {
|
||||
int thresh = TransientSessionKeyManager.LOW_THRESHOLD;
|
||||
boolean hasElg = false;
|
||||
boolean hasEC = false;
|
||||
boolean hasPQ = false;
|
||||
int pqType = 0;
|
||||
// router may be null in unit tests, avoid NPEs in ratchet
|
||||
// we won't actually be using any SKM anyway
|
||||
if (opts != null && _context.router() != null) {
|
||||
@ -620,10 +624,18 @@ class ClientConnectionRunner {
|
||||
if (senc != null) {
|
||||
String[] senca = DataHelper.split(senc, ",");
|
||||
for (String sencaa : senca) {
|
||||
if (sencaa.equals("0"))
|
||||
if (sencaa.equals("0")) {
|
||||
hasElg = true;
|
||||
else if (sencaa.equals("4"))
|
||||
} else if (sencaa.equals("4")) {
|
||||
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 {
|
||||
hasElg = true;
|
||||
@ -632,6 +644,10 @@ class ClientConnectionRunner {
|
||||
hasElg = true;
|
||||
}
|
||||
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);
|
||||
if (hasEC) {
|
||||
RatchetSKM rskm = new RatchetSKM(_context, dest);
|
||||
@ -639,6 +655,16 @@ class ClientConnectionRunner {
|
||||
} else {
|
||||
_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 {
|
||||
if (hasEC) {
|
||||
_sessionKeyManager = new RatchetSKM(_context, dest);
|
||||
|
@ -16,6 +16,7 @@ import com.southernstorm.noise.protocol.HandshakeState;
|
||||
|
||||
import net.i2p.crypto.EncType;
|
||||
import net.i2p.crypto.HKDF;
|
||||
import net.i2p.crypto.KeyFactory;
|
||||
import net.i2p.crypto.SessionKeyManager;
|
||||
import net.i2p.data.Base64;
|
||||
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.GarlicClove;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.crypto.pqc.MLKEM;
|
||||
import static net.i2p.router.crypto.ratchet.RatchetPayload.*;
|
||||
import net.i2p.router.LeaseSetKeys;
|
||||
import net.i2p.router.Router;
|
||||
@ -53,6 +55,7 @@ public final class ECIESAEADEngine {
|
||||
private final RouterContext _context;
|
||||
private final Log _log;
|
||||
private final MuxedEngine _muxedEngine;
|
||||
private final MuxedPQEngine _muxedPQEngine;
|
||||
private final HKDF _hkdf;
|
||||
private final Elg2KeyFactory _edhThread;
|
||||
private boolean _isRunning;
|
||||
@ -86,6 +89,24 @@ public final class ECIESAEADEngine {
|
||||
private static final String INFO_0 = "SessionReplyTags";
|
||||
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.
|
||||
* Will still work without, will just generate inline.
|
||||
@ -96,6 +117,7 @@ public final class ECIESAEADEngine {
|
||||
_context = ctx;
|
||||
_log = _context.logManager().getLog(ECIESAEADEngine.class);
|
||||
_muxedEngine = new MuxedEngine(ctx);
|
||||
_muxedPQEngine = new MuxedPQEngine(ctx);
|
||||
_hkdf = new HKDF(ctx);
|
||||
_edhThread = new Elg2KeyFactory(ctx);
|
||||
|
||||
@ -147,6 +169,18 @@ public final class ECIESAEADEngine {
|
||||
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
|
||||
* and using tags from the specified key manager.
|
||||
@ -175,8 +209,7 @@ public final class ECIESAEADEngine {
|
||||
|
||||
private CloveSet x_decrypt(byte data[], PrivateKey targetPrivateKey,
|
||||
RatchetSKM keyManager) throws DataFormatException {
|
||||
if (targetPrivateKey.getType() != EncType.ECIES_X25519)
|
||||
throw new IllegalArgumentException();
|
||||
checkType(targetPrivateKey.getType());
|
||||
if (data == null) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error("Null data being decrypted?");
|
||||
return null;
|
||||
@ -267,14 +300,22 @@ public final class ECIESAEADEngine {
|
||||
if (shouldDebug)
|
||||
_log.debug("Decrypting ES with tag: " + st.toBase64() + " key: " + key + ": " + data.length + " bytes");
|
||||
decrypted = decryptExistingSession(tag, data, key, targetPrivateKey, keyManager);
|
||||
} else if (data.length >= MIN_NSR_SIZE) {
|
||||
if (shouldDebug)
|
||||
_log.debug("Decrypting NSR with tag: " + st.toBase64() + " key: " + key + ": " + data.length + " bytes");
|
||||
decrypted = decryptNewSessionReply(tag, data, state, keyManager);
|
||||
} else {
|
||||
decrypted = null;
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("ECIES decrypt fail, tag found but no state and too small for NSR: " + data.length + " bytes");
|
||||
// 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)
|
||||
_log.debug("Decrypting NSR with tag: " + st.toBase64() + " key: " + key + ": " + data.length + " bytes");
|
||||
decrypted = decryptNewSessionReply(tag, data, state, keyManager);
|
||||
} else {
|
||||
decrypted = null;
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("NSR decrypt fail, tag: " + st.toBase64() + " but packet too small: " + data.length + " bytes, min is " +
|
||||
min + " on state " + state);
|
||||
}
|
||||
}
|
||||
if (decrypted != null) {
|
||||
_context.statManager().updateFrequency("crypto.eciesAEAD.decryptExistingSession");
|
||||
@ -316,8 +357,10 @@ public final class ECIESAEADEngine {
|
||||
private CloveSet x_decryptSlow(byte data[], PrivateKey targetPrivateKey,
|
||||
RatchetSKM keyManager) throws DataFormatException {
|
||||
CloveSet decrypted;
|
||||
EncType type = targetPrivateKey.getType();
|
||||
int minns = getMinNSSize(type);
|
||||
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)
|
||||
decrypted = decryptNewSession_N(data, targetPrivateKey, keyManager);
|
||||
else
|
||||
@ -333,11 +376,116 @@ public final class ECIESAEADEngine {
|
||||
} else {
|
||||
decrypted = null;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*
|
||||
@ -383,8 +531,12 @@ public final class ECIESAEADEngine {
|
||||
System.arraycopy(pk.getData(), 0, data, 0, KEYLEN);
|
||||
|
||||
HandshakeState state;
|
||||
EncType type = targetPrivateKey.getType();
|
||||
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) {
|
||||
throw new IllegalStateException("bad proto", gse);
|
||||
}
|
||||
@ -392,9 +544,13 @@ public final class ECIESAEADEngine {
|
||||
targetPrivateKey.toPublic().getData(), 0);
|
||||
state.start();
|
||||
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);
|
||||
DHState hyb = state.getRemoteHybridKeyPair();
|
||||
if (hyb != null) {
|
||||
payloadlen -= hyb.getPublicKeyLength() + MACLEN;
|
||||
}
|
||||
byte[] payload = new byte[payloadlen];
|
||||
try {
|
||||
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
|
||||
// logged at INFO in caller
|
||||
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
|
||||
System.arraycopy(xx, 0, data, 0, KEYLEN - 1);
|
||||
data[KEYLEN - 1] = xx31;
|
||||
@ -467,7 +623,7 @@ public final class ECIESAEADEngine {
|
||||
state.destroy();
|
||||
} else {
|
||||
// tell the SKM
|
||||
PublicKey alice = new PublicKey(EncType.ECIES_X25519, alicePK);
|
||||
PublicKey alice = new PublicKey(type, alicePK);
|
||||
keyManager.createSession(alice, null, state, null);
|
||||
setResponseTimerNS(alice, pc.cloveSet, keyManager);
|
||||
}
|
||||
@ -638,8 +794,13 @@ public final class ECIESAEADEngine {
|
||||
state.mixHash(tag, 0, TAGLEN);
|
||||
if (_log.shouldDebug())
|
||||
_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 {
|
||||
state.readMessage(data, 8, 48, ZEROLEN, 0);
|
||||
state.readMessage(data, 8, tmplen, ZEROLEN, 0);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
if (_log.shouldWarn()) {
|
||||
_log.warn("Decrypt fail NSR part 1", gse);
|
||||
@ -667,9 +828,16 @@ public final class ECIESAEADEngine {
|
||||
byte[] encpayloadkey = new byte[32];
|
||||
_hkdf.calculate(split.k_ba.getData(), ZEROLEN, INFO_6, encpayloadkey);
|
||||
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 {
|
||||
rcvr.decryptWithAd(hash, data, TAGLEN + KEYLEN + MACLEN, payload, 0, payload.length + MACLEN);
|
||||
rcvr.decryptWithAd(hash, data, off, payload, 0, plen + MACLEN);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
if (_log.shouldWarn()) {
|
||||
_log.warn("Decrypt fail NSR part 2", gse);
|
||||
@ -714,7 +882,7 @@ public final class ECIESAEADEngine {
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
if (pc == null)
|
||||
@ -872,8 +1040,7 @@ public final class ECIESAEADEngine {
|
||||
private byte[] x_encrypt(CloveSet cloves, PublicKey target, Destination to, PrivateKey priv,
|
||||
RatchetSKM keyManager,
|
||||
ReplyCallback callback) {
|
||||
if (target.getType() != EncType.ECIES_X25519)
|
||||
throw new IllegalArgumentException();
|
||||
checkType(target.getType());
|
||||
if (Arrays.equals(target.getData(), NULLPK)) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Zero static key target");
|
||||
@ -904,6 +1071,7 @@ public final class ECIESAEADEngine {
|
||||
}
|
||||
if (_log.shouldDebug())
|
||||
_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);
|
||||
}
|
||||
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,
|
||||
RatchetSKM keyManager,
|
||||
ReplyCallback callback) {
|
||||
EncType type = target.getType();
|
||||
if (type != priv.getType())
|
||||
throw new IllegalArgumentException("Key mismatch " + target + ' ' + priv);
|
||||
HandshakeState state;
|
||||
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) {
|
||||
throw new IllegalStateException("bad proto", gse);
|
||||
}
|
||||
@ -962,7 +1134,12 @@ public final class ECIESAEADEngine {
|
||||
|
||||
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 {
|
||||
state.writeMessage(enc, 0, payload, 0, payload.length);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
@ -1071,7 +1248,12 @@ public final class ECIESAEADEngine {
|
||||
byte[] payload = createPayload(cloves, 0, NSR_OVERHEAD);
|
||||
|
||||
// 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);
|
||||
try {
|
||||
state.writeMessage(enc, TAGLEN, ZEROLEN, 0, 0);
|
||||
@ -1103,8 +1285,12 @@ public final class ECIESAEADEngine {
|
||||
byte[] encpayloadkey = new byte[32];
|
||||
_hkdf.calculate(split.k_ba.getData(), ZEROLEN, INFO_6, encpayloadkey);
|
||||
sender.initializeKey(encpayloadkey, 0);
|
||||
int off = TAGLEN + KEYLEN + MACLEN;
|
||||
if (hyb != null) {
|
||||
off += hyb.getPublicKeyLength() + MACLEN;
|
||||
}
|
||||
try {
|
||||
sender.encryptWithAd(hash, payload, 0, enc, TAGLEN + KEYLEN + MACLEN, payload.length);
|
||||
sender.encryptWithAd(hash, payload, 0, enc, off, payload.length);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Encrypt fail NSR part 2", gse);
|
||||
@ -1488,7 +1674,7 @@ public final class ECIESAEADEngine {
|
||||
return;
|
||||
if (!ls2.isCurrent(Router.CLOCK_FUDGE_FACTOR))
|
||||
continue;
|
||||
PublicKey pk = ls2.getEncryptionKey(LeaseSetKeys.SET_EC);
|
||||
PublicKey pk = ls2.getEncryptionKey(getEncTypeSet(from.getType()));
|
||||
if (!from.equals(pk))
|
||||
continue;
|
||||
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 DecayingHashSet _replayFilter;
|
||||
private final Destination _destination;
|
||||
private final EncType _type;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @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);
|
||||
_log = context.logManager().getLog(RatchetSKM.class);
|
||||
_context = context;
|
||||
_destination = dest;
|
||||
_type = type;
|
||||
_outboundSessions = new ConcurrentHashMap<PublicKey, OutboundSession>(64);
|
||||
_pendingOutboundSessions = new HashMap<PublicKey, List<OutboundSession>>(64);
|
||||
_inboundTagSets = new ConcurrentHashMap<RatchetSessionTag, RatchetTagSet>(128);
|
||||
@ -146,6 +160,15 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
|
||||
return _destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* The EncType for this SKM
|
||||
*
|
||||
* @since 0.9.67
|
||||
*/
|
||||
public EncType getType() {
|
||||
return _type;
|
||||
}
|
||||
|
||||
/** RatchetTagSet */
|
||||
private Set<RatchetTagSet> getRatchetTagSets() {
|
||||
synchronized (_inboundTagSets) {
|
||||
@ -201,8 +224,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
|
||||
*/
|
||||
boolean createSession(PublicKey target, Destination d, HandshakeState state, ReplyCallback callback) {
|
||||
EncType type = target.getType();
|
||||
if (type != EncType.ECIES_X25519)
|
||||
throw new IllegalArgumentException("Bad public key type " + type);
|
||||
if (type != _type)
|
||||
throw new IllegalArgumentException("Bad public key type " + type + " expected " + _type);
|
||||
OutboundSession sess = new OutboundSession(target, d, null, state, callback);
|
||||
boolean isInbound = state.getRole() == HandshakeState.RESPONDER;
|
||||
if (isInbound) {
|
||||
@ -247,8 +270,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
|
||||
boolean updateSession(PublicKey target, HandshakeState oldState, HandshakeState state,
|
||||
ReplyCallback callback, SplitKeys split) {
|
||||
EncType type = target.getType();
|
||||
if (type != EncType.ECIES_X25519)
|
||||
throw new IllegalArgumentException("Bad public key type " + type);
|
||||
if (type != _type)
|
||||
throw new IllegalArgumentException("Bad public key type " + type + " expected " + _type);
|
||||
boolean isInbound = state.getRole() == HandshakeState.RESPONDER;
|
||||
if (isInbound) {
|
||||
// we are Bob, NSR sent
|
||||
|
@ -32,6 +32,7 @@ import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.LeaseSetKeys;
|
||||
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.RatchetSKM;
|
||||
import net.i2p.router.crypto.ratchet.RatchetSessionTag;
|
||||
@ -46,13 +47,13 @@ import net.i2p.util.Log;
|
||||
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 minTagOverride 0 for no override, > 0 to override SKM's settings
|
||||
*/
|
||||
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;
|
||||
SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(local);
|
||||
if (skm == null)
|
||||
@ -275,7 +276,7 @@ public class GarlicMessageBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* ECIES_X25519 only.
|
||||
* ECIES_X25519 and PQ only.
|
||||
* Called by OCMJH only.
|
||||
*
|
||||
* @param ctx scope
|
||||
@ -289,7 +290,8 @@ public class GarlicMessageBuilder {
|
||||
Hash from, Destination to, SessionKeyManager skm,
|
||||
ReplyCallback callback) {
|
||||
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();
|
||||
Log log = ctx.logManager().getLog(GarlicMessageBuilder.class);
|
||||
GarlicMessage msg = new GarlicMessage(ctx);
|
||||
@ -300,7 +302,7 @@ public class GarlicMessageBuilder {
|
||||
log.warn("No LSK for " + from.toBase32());
|
||||
return null;
|
||||
}
|
||||
PrivateKey priv = lsk.getDecryptionKey(EncType.ECIES_X25519);
|
||||
PrivateKey priv = lsk.getDecryptionKey(type);
|
||||
if (priv == null) {
|
||||
if (log.shouldWarn())
|
||||
log.warn("No key for " + from.toBase32());
|
||||
@ -312,6 +314,9 @@ public class GarlicMessageBuilder {
|
||||
rskm = (RatchetSKM) skm;
|
||||
} else if (skm instanceof MuxedSKM) {
|
||||
rskm = ((MuxedSKM) skm).getECSKM();
|
||||
} else if (skm instanceof MuxedPQSKM) {
|
||||
MuxedPQSKM mskm = (MuxedPQSKM) skm;
|
||||
rskm = type.isPQ() ? mskm.getPQSKM() : mskm.getECSKM();
|
||||
} else {
|
||||
if (log.shouldWarn())
|
||||
log.warn("No SKM for " + from.toBase32());
|
||||
@ -338,7 +343,7 @@ public class GarlicMessageBuilder {
|
||||
|
||||
/**
|
||||
* Encrypt from an anonymous source.
|
||||
* ECIES_X25519 only.
|
||||
* ECIES_X25519 only. PQ not supported.
|
||||
* Called by MessageWrapper only.
|
||||
*
|
||||
* @param ctx scope
|
||||
@ -348,7 +353,7 @@ public class GarlicMessageBuilder {
|
||||
*/
|
||||
public static GarlicMessage buildECIESMessage(RouterContext ctx, GarlicConfig config) {
|
||||
PublicKey key = config.getRecipientPublicKey();
|
||||
if (key.getType() != EncType.ECIES_X25519)
|
||||
if (!LeaseSetKeys.SET_EC_PQ_ALL.contains(key.getType()))
|
||||
throw new IllegalArgumentException();
|
||||
Log log = ctx.logManager().getLog(GarlicMessageBuilder.class);
|
||||
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.GarlicMessage;
|
||||
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.RatchetSKM;
|
||||
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 skm use tags from this session key manager
|
||||
@ -65,6 +66,8 @@ public class GarlicMessageParser {
|
||||
rskm = (RatchetSKM) skm;
|
||||
} else if (skm instanceof MuxedSKM) {
|
||||
rskm = ((MuxedSKM) skm).getECSKM();
|
||||
} else if (skm instanceof MuxedPQSKM) {
|
||||
rskm = ((MuxedPQSKM) skm).getECSKM();
|
||||
} else {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("No SKM to decrypt ECIES");
|
||||
@ -80,6 +83,27 @@ public class GarlicMessageParser {
|
||||
_log.warn("ECIES decrypt fail");
|
||||
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 {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Can't decrypt with key type " + type);
|
||||
@ -112,7 +136,7 @@ public class GarlicMessageParser {
|
||||
/**
|
||||
* 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 skm use tags from this session key manager
|
||||
* @return null on error
|
||||
@ -125,6 +149,10 @@ public class GarlicMessageParser {
|
||||
if (skm instanceof MuxedSKM) {
|
||||
MuxedSKM mskm = (MuxedSKM) skm;
|
||||
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) {
|
||||
// unlikely, if we have two keys we should have a MuxedSKM
|
||||
RatchetSKM rskm = (RatchetSKM) skm;
|
||||
|
@ -65,15 +65,24 @@ public class GarlicMessageReceiver {
|
||||
if (keys != null && skm != null) {
|
||||
decryptionKey = keys.getDecryptionKey();
|
||||
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())
|
||||
_log.warn("No key to decrypt for " + _clientDestination.toBase32());
|
||||
return;
|
||||
}
|
||||
// ElG + PQ disallowed
|
||||
if (decryptionKey == null) {
|
||||
// swap
|
||||
decryptionKey = decryptionKey2;
|
||||
decryptionKey2 = null;
|
||||
if (decryptionKey3 != null) {
|
||||
// PQ first if present
|
||||
decryptionKey = decryptionKey3;
|
||||
} else {
|
||||
// EC only
|
||||
decryptionKey = decryptionKey2;
|
||||
decryptionKey2 = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
|
@ -124,7 +124,7 @@ class OutboundClientMessageJobHelper {
|
||||
SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(from);
|
||||
if (skm == null)
|
||||
return null;
|
||||
boolean isECIES = recipientPK.getType() == EncType.ECIES_X25519;
|
||||
boolean isECIES = recipientPK.getType() != EncType.ELGAMAL_2048;
|
||||
// force ack off if ECIES
|
||||
boolean ackInGarlic = isECIES ? false : requireAck;
|
||||
GarlicConfig config = createGarlicConfig(ctx, replyToken, expiration, recipientPK, dataClove,
|
||||
|
@ -359,7 +359,7 @@ public class IterativeSearchJob extends FloodSearchJob {
|
||||
}
|
||||
LeaseSetKeys lsk = ctx.keyManager().getKeys(_fromLocalDest);
|
||||
supportsRatchet = lsk != null &&
|
||||
lsk.isSupported(EncType.ECIES_X25519) &&
|
||||
(lsk.isSupported(EncType.ECIES_X25519) || lsk.getPQDecryptionKey() != null) &&
|
||||
DatabaseLookupMessage.supportsRatchetReplies(ri);
|
||||
supportsElGamal = !supportsRatchet &&
|
||||
lsk != null &&
|
||||
|
@ -17,6 +17,7 @@ import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.RouterContext;
|
||||
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.RatchetSKM;
|
||||
import net.i2p.router.crypto.ratchet.RatchetSessionTag;
|
||||
@ -242,6 +243,8 @@ public class MessageWrapper {
|
||||
rskm = (RatchetSKM) skm;
|
||||
} else if (skm instanceof MuxedSKM) {
|
||||
rskm = ((MuxedSKM) skm).getECSKM();
|
||||
} else if (skm instanceof MuxedPQSKM) {
|
||||
rskm = ((MuxedPQSKM) skm).getECSKM();
|
||||
} else {
|
||||
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.TunnelPoolSettings;
|
||||
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.networkdb.kademlia.MessageWrapper;
|
||||
import net.i2p.router.networkdb.kademlia.MessageWrapper.OneTimeSession;
|
||||
@ -318,6 +319,9 @@ abstract class BuildRequestor {
|
||||
} else if (replySKM instanceof MuxedSKM) {
|
||||
MuxedSKM mskm = (MuxedSKM) replySKM;
|
||||
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 {
|
||||
// non-EC client, shouldn't happen, checked at top of createTunnelBuildMessage() below
|
||||
if (log.shouldWarn())
|
||||
|
@ -13,6 +13,7 @@ import net.i2p.router.OutNetMessage;
|
||||
import net.i2p.router.ReplyJob;
|
||||
import net.i2p.router.RouterContext;
|
||||
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.RatchetSessionTag;
|
||||
import net.i2p.router.crypto.ratchet.RatchetSKM;
|
||||
@ -368,6 +369,8 @@ class TestJob extends JobImpl {
|
||||
rskm = (RatchetSKM) skm;
|
||||
} else if (skm instanceof MuxedSKM) {
|
||||
rskm = ((MuxedSKM) skm).getECSKM();
|
||||
} else if (skm instanceof MuxedPQSKM) {
|
||||
rskm = ((MuxedPQSKM) skm).getECSKM();
|
||||
} else {
|
||||
// shouldn't happen
|
||||
rskm = null;
|
||||
|
Reference in New Issue
Block a user