Skip to content

Commit

Permalink
use secure hash for ConnectionSource, the key in the connection regis…
Browse files Browse the repository at this point in the history
…try hashtable
  • Loading branch information
ptrd committed Jan 11, 2025
1 parent da84ebe commit b0733d7
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ javadoc {
}

repositories {
flatDir {
dirs "$rootDir/localdeps"
}
mavenCentral()
}

Expand Down
3 changes: 2 additions & 1 deletion core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ plugins {
id 'buildlogic.java-common-conventions'
}


dependencies {
implementation group: 'tech.kwik', name: 'agent15', version: "$agent15_version"

// https://mvnrepository.com/artifact/at.favre.lib/hkdf
implementation group: 'at.favre.lib', name: 'hkdf', version: '2.0.0'

implementation group: 'io.whitfin', name: 'siphash', version: '2.0.0-auto-module'
}

task includeVersion {
Expand Down
1 change: 1 addition & 0 deletions core/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module tech.kwik.core {
requires tech.kwik.agent15;
requires at.favre.lib.hkdf;
requires io.whitfin.siphash;

exports tech.kwik.core;
exports tech.kwik.core.concurrent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,23 @@
package tech.kwik.core.server.impl;

import tech.kwik.core.util.Bytes;
import tech.kwik.core.util.SecureHash;

import java.util.Arrays;

public class ConnectionSource {

private final byte[] dcid;
private final int hashCode;

public ConnectionSource(byte[] dcid) {
public ConnectionSource(byte[] dcid, SecureHash secureHash) {
this.dcid = dcid;
hashCode = secureHash.generateHashCode(dcid);
}

@Override
public int hashCode() {
return Arrays.hashCode(dcid);
return hashCode;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
import tech.kwik.core.log.Logger;
import tech.kwik.core.server.ServerConnectionRegistry;
import tech.kwik.core.util.Bytes;
import tech.kwik.core.util.SecureHash;

import java.net.InetSocketAddress;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.Comparator;
import java.util.List;
Expand All @@ -43,31 +45,40 @@ public class ServerConnectionRegistryImpl implements ServerConnectionRegistry {
private final Map<ConnectionSource, ServerConnectionProxy> currentConnections;
private final Lock checkEmptyLock;
private final Condition allConnectionsClosed;
private final SecureHash secureHash;

ServerConnectionRegistryImpl(Logger log) {
this.log = log;
currentConnections = new ConcurrentHashMap<>();

checkEmptyLock = new ReentrantLock();
allConnectionsClosed = checkEmptyLock.newCondition();

byte[] seed = new byte[16];
new SecureRandom().nextBytes(seed);
secureHash = new SecureHash(seed);
}

@Override
public void registerConnection(ServerConnectionProxy connection, byte[] connectionId) {
currentConnections.put(new ConnectionSource(connectionId), connection);
currentConnections.put(connectionSource(connectionId), connection);
}

private ConnectionSource connectionSource(byte[] connectionId) {
return new ConnectionSource(connectionId, secureHash);
}

@Override
public void deregisterConnection(ServerConnectionProxy connection, byte[] connectionId) {
currentConnections.remove(new ConnectionSource(connectionId));
currentConnections.remove(connectionSource(connectionId));
checkAllConnectionsClosed();
}

@Override
public void registerAdditionalConnectionId(byte[] currentConnectionId, byte[] newConnectionId) {
ServerConnectionProxy connection = currentConnections.get(new ConnectionSource(currentConnectionId));
ServerConnectionProxy connection = currentConnections.get(connectionSource(currentConnectionId));
if (connection != null) {
currentConnections.put(new ConnectionSource(newConnectionId), connection);
currentConnections.put(connectionSource(newConnectionId), connection);
}
else {
log.error("Cannot add additional cid to non-existing connection " + Bytes.bytesToHex(currentConnectionId));
Expand All @@ -76,21 +87,21 @@ public void registerAdditionalConnectionId(byte[] currentConnectionId, byte[] ne

@Override
public void deregisterConnectionId(byte[] connectionId) {
currentConnections.remove(new ConnectionSource(connectionId));
currentConnections.remove(connectionSource(connectionId));
checkAllConnectionsClosed();
}

Optional<ServerConnectionProxy> isExistingConnection(InetSocketAddress clientAddress, byte[] dcid) {
return Optional.ofNullable(currentConnections.get(new ConnectionSource(dcid)));
return Optional.ofNullable(currentConnections.get(connectionSource(dcid)));
}

ServerConnectionProxy removeConnection(ServerConnectionImpl connection) {
// Remove the entry this is registered with the original dcid
ServerConnectionProxy removed = currentConnections.remove(new ConnectionSource(connection.getOriginalDestinationConnectionId()));
ServerConnectionProxy removed = currentConnections.remove(connectionSource(connection.getOriginalDestinationConnectionId()));

// Remove all entries that are registered with the active cids
List<ServerConnectionProxy> removedConnections = connection.getActiveConnectionIds().stream()
.map(cid -> new ConnectionSource(cid))
.map(cid -> connectionSource(cid))
.map(cs -> currentConnections.remove(cs))
.filter(Objects::nonNull)
.collect(Collectors.toList());
Expand Down
36 changes: 36 additions & 0 deletions core/src/main/java/tech/kwik/core/util/SecureHash.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright © 2025 Peter Doornbosch
*
* This file is part of Kwik, an implementation of the QUIC protocol in Java.
*
* Kwik is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* Kwik is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
* more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package tech.kwik.core.util;

import io.whitfin.siphash.SipHasher;
import io.whitfin.siphash.SipHasherContainer;

public class SecureHash {

private final SipHasherContainer container;

public SecureHash(byte[] key) {
container = SipHasher.container(key);
}

public int generateHashCode(byte[] dcid) {
long longHash = container.hash(dcid);
return (int)(longHash ^ (longHash >>> 32));
}
}
82 changes: 82 additions & 0 deletions core/src/test/java/tech/kwik/core/util/SecureHashTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright © 2025 Peter Doornbosch
*
* This file is part of Kwik, an implementation of the QUIC protocol in Java.
*
* Kwik is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* Kwik is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
* more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package tech.kwik.core.util;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import tech.kwik.core.test.ByteUtils;

import java.security.SecureRandom;

import static org.assertj.core.api.Assertions.assertThat;


class SecureHashTest {

private SecureHash secureHash;

@BeforeEach
void setUp() {
byte[] key = "1234567890123456".getBytes();
secureHash = new SecureHash(key);
}

@Test
void generatingHashForSameInputShouldReturnSameHash() {
// Given
byte[] input = ByteUtils.hexToBytes("cd8330cac6107e88");

// When
int hash1 = secureHash.generateHashCode(input);
int hash2 = secureHash.generateHashCode(input);

// Then
assertThat(hash1).isEqualTo(hash2);
}

@Test
void generatingHashForDifferentInputsShouldReturnDifferentHashes() {
// Given
byte[] input1 = ByteUtils.hexToBytes("cd8330cac6107e88");
byte[] input2 = ByteUtils.hexToBytes("07e89cd8330cac61");
// When
int hash1 = secureHash.generateHashCode(input1);
int hash2 = secureHash.generateHashCode(input2);

// Then
assertThat(hash1).isNotEqualTo(hash2);
}

@Test
void generatingHashForSameInputButDifferentSeedsShouldReturnDifferentHash() {
// Given
byte[] key = new byte[16];
new SecureRandom().nextBytes(key);
SecureHash secureHash2 = new SecureHash(key);

byte[] input = ByteUtils.hexToBytes("cd8330cac6107e88");

// When
int hash1 = secureHash.generateHashCode(input);
int hash2 = secureHash2.generateHashCode(input);

// Then
assertThat(hash1).isNotEqualTo(hash2);
}
}

0 comments on commit b0733d7

Please sign in to comment.