Skip to content

Commit

Permalink
Guard against overwriting newer IPNS records
Browse files Browse the repository at this point in the history
Put record in local store during publish
  • Loading branch information
ianopolous committed Dec 14, 2023
1 parent e4cfae8 commit 6d7c107
Show file tree
Hide file tree
Showing 7 changed files with 37 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ private String hashToKey(Multihash hash) {
}

@Override
public Optional<IpnsRecord> get(Cid peerId) {
public Optional<IpnsRecord> get(Multihash peerId) {
String selectSQL = "SELECT raw, sequence, ttlNanos, expiryUTC, val FROM " + RECORD_TABLE + " WHERE peerId=?";
try (PreparedStatement pstmt = connection.prepareStatement(selectSQL)) {
pstmt.setString(1, hashToKey(peerId));
Expand Down
11 changes: 10 additions & 1 deletion src/main/java/org/peergos/protocol/dht/Kademlia.java
Original file line number Diff line number Diff line change
Expand Up @@ -331,10 +331,19 @@ private boolean putValue(Multihash publisher,
public CompletableFuture<Void> publishValue(Multihash publisher,
byte[] signedRecord,
Host us) {
byte[] key = IPNS.getKey(publisher);
Optional<IpnsMapping> parsed = IPNS.parseAndValidateIpnsEntry(key, signedRecord);
if (parsed.isEmpty() || !parsed.get().publisher.equals(publisher))
throw new IllegalStateException("Tried to publish invalid INS record for " + publisher);
Optional<IpnsRecord> existing = engine.getRecord(publisher);
// don't overwrite 'newer' record
if (existing.isEmpty() || parsed.get().value.compareTo(existing.get()) > 0) {
engine.addRecord(publisher, parsed.get().value);
}

Set<Multihash> publishes = Collections.synchronizedSet(new HashSet<>());
int minPublishes = 20;

byte[] key = IPNS.getKey(publisher);
Id keyId = Id.create(Hash.sha256(key), 256);
SortedSet<RoutingEntry> toQuery = new TreeSet<>((a, b) -> compareKeys(a, b, keyId));
List<PeerAddresses> localClosest = engine.getKClosestPeers(key);
Expand Down
16 changes: 14 additions & 2 deletions src/main/java/org/peergos/protocol/dht/KademliaEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.google.protobuf.*;
import com.offbynull.kademlia.*;
import io.ipfs.cid.*;
import io.ipfs.multiaddr.*;
import io.ipfs.multihash.Multihash;
import io.libp2p.core.*;
import io.libp2p.core.Stream;
Expand Down Expand Up @@ -97,12 +96,25 @@ public List<PeerAddresses> getKClosestPeers(byte[] key) {
.collect(Collectors.toList());
}

public void addRecord(Multihash publisher, IpnsRecord record) {
ipnsStore.put(publisher, record);
}

public Optional<IpnsRecord> getRecord(Multihash publisher) {
return ipnsStore.get(publisher);
}

public void receiveRequest(Dht.Message msg, PeerId source, Stream stream) {
responderReceivedBytes.inc(msg.getSerializedSize());
switch (msg.getType()) {
case PUT_VALUE: {
Optional<IpnsMapping> mapping = IPNS.validateIpnsEntry(msg);
Optional<IpnsMapping> mapping = IPNS.parseAndValidateIpnsEntry(msg);
if (mapping.isPresent()) {
Optional<IpnsRecord> existing = ipnsStore.get(mapping.get().publisher);
if (existing.isPresent() && mapping.get().value.compareTo(existing.get()) < 0) {
// don't add 'older' record
return;
}
ipnsStore.put(mapping.get().publisher, mapping.get().value);
stream.writeAndFlush(msg);
responderSentBytes.inc(msg.getSerializedSize());
Expand Down
3 changes: 1 addition & 2 deletions src/main/java/org/peergos/protocol/dht/RamRecordStore.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.peergos.protocol.dht;

import io.ipfs.cid.*;
import io.ipfs.multihash.*;
import org.peergos.protocol.ipns.*;

Expand All @@ -19,7 +18,7 @@ public void put(Multihash peerId, IpnsRecord record) {
}

@Override
public Optional<IpnsRecord> get(Cid peerId) {
public Optional<IpnsRecord> get(Multihash peerId) {
return Optional.ofNullable(records.get(peerId));
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/peergos/protocol/dht/RecordStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public interface RecordStore extends AutoCloseable {

void put(Multihash peerId, IpnsRecord record);

Optional<IpnsRecord> get(Cid peerId);
Optional<IpnsRecord> get(Multihash peerId);

void remove(Multihash peerId);
}
2 changes: 1 addition & 1 deletion src/main/java/org/peergos/protocol/ipns/GetResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static GetResult fromProtobuf(Dht.Message msg) {
.map(PeerAddresses::fromProtobuf)
.collect(Collectors.toList());
Optional<IpnsMapping> record = msg.hasRecord() ?
IPNS.validateIpnsEntry(msg) :
IPNS.parseAndValidateIpnsEntry(msg) :
Optional.empty();
return new GetResult(record, closerPeers);
}
Expand Down
14 changes: 9 additions & 5 deletions src/main/java/org/peergos/protocol/ipns/IPNS.java
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,22 @@ public static byte[] createSigV2Data(byte[] data) {
}
}

public static Optional<IpnsMapping> validateIpnsEntry(Dht.Message msg) {
public static Optional<IpnsMapping> parseAndValidateIpnsEntry(Dht.Message msg) {
if (! msg.hasRecord() || msg.getRecord().getValue().size() > IPNS.MAX_RECORD_SIZE)
return Optional.empty();
if (! msg.getKey().equals(msg.getRecord().getKey()))
return Optional.empty();
if (! msg.getRecord().getKey().startsWith(ByteString.copyFrom("/ipns/".getBytes(StandardCharsets.UTF_8))))
byte[] entryBytes = msg.getRecord().getValue().toByteArray();
return parseAndValidateIpnsEntry(msg.getRecord().getKey().toByteArray(), entryBytes);
}

public static Optional<IpnsMapping> parseAndValidateIpnsEntry(byte[] key, byte[] entryBytes) {
if (! Arrays.equals(Arrays.copyOfRange(key, 0, 6), "/ipns/".getBytes(StandardCharsets.UTF_8)))
return Optional.empty();
byte[] cidBytes = msg.getRecord().getKey().substring(6).toByteArray();
byte[] cidBytes = Arrays.copyOfRange(key, 6, key.length);
Multihash signer = Multihash.deserialize(cidBytes);
try {
Ipns.IpnsEntry entry = Ipns.IpnsEntry.parseFrom(msg.getRecord().getValue());
Ipns.IpnsEntry entry = Ipns.IpnsEntry.parseFrom(entryBytes);
if (! entry.hasSignatureV2() || ! entry.hasData())
return Optional.empty();
PubKey pub;
Expand Down Expand Up @@ -137,7 +142,6 @@ public static Optional<IpnsMapping> validateIpnsEntry(Dht.Message msg) {
LocalDateTime expiry = LocalDateTime.parse(new String(validity).substring(0, validity.length - 1), IPNS.rfc3339nano);
if (expiry.isBefore(LocalDateTime.now()))
return Optional.empty();
byte[] entryBytes = msg.getRecord().getValue().toByteArray();
IpnsRecord record = new IpnsRecord(entryBytes, entry.getSequence(), entry.getTtl(), expiry, entry.getValue().toByteArray());
return Optional.of(new IpnsMapping(signer, record));
} catch (InvalidProtocolBufferException e) {
Expand Down

0 comments on commit 6d7c107

Please sign in to comment.