From 666254a55e4b824f2ed750a50d91bffd9c66745b Mon Sep 17 00:00:00 2001 From: "qodo-code-review[bot]" <151058649+qodo-code-review[bot]@users.noreply.github.com> Date: Sat, 5 Sep 2026 03:29:29 +0000 Subject: [PATCH 1/5] =?UTF-8?q?fix:=202=20findings=20=E2=80=94=20Reject=20?= =?UTF-8?q?offline=20keys=20for=20Datagram1;=20Reject=20encrypted=20of?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reject offline keys for Datagram1 - Reject encrypted offline sessions --- client/internal/sam/protocol.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/internal/sam/protocol.go b/client/internal/sam/protocol.go index 7bcbc3b..5a4a6a2 100644 --- a/client/internal/sam/protocol.go +++ b/client/internal/sam/protocol.go @@ -149,6 +149,14 @@ func (s *Server) createSession(ctx context.Context, connection *serverConnection if meta, ok := local.OfflineSignature(); ok { meta := meta offline = &meta + if style == styleDatagram { + local.ReleaseSensitive() + return connection.writeLine("SESSION STATUS RESULT=INVALID_KEY") + } + if policy.Encrypted { + local.ReleaseSensitive() + return connection.writeLine("SESSION STATUS RESULT=INVALID_KEY") + } } private, err := encodePrivateDestination(local) if err != nil { From c303574b6ea266f8d2450fff0b835bfc1165e4f6 Mon Sep 17 00:00:00 2001 From: "qodo-code-review[bot]" <151058649+qodo-code-review[bot]@users.noreply.github.com> Date: Sat, 5 Sep 2026 03:29:33 +0000 Subject: [PATCH 2/5] fix: Use explicit Datagram verification time --- client/internal/sam/datagram.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/internal/sam/datagram.go b/client/internal/sam/datagram.go index 0c0694a..daece4d 100644 --- a/client/internal/sam/datagram.go +++ b/client/internal/sam/datagram.go @@ -5,6 +5,7 @@ import ( "io" "strconv" "strings" + "time" "gosuda.org/ivnp/foundation" "gosuda.org/ivnp/interfaces/destination" @@ -175,7 +176,7 @@ func (s *samSession) parseReceivedDatagram(delivery networking.StreamingTunnelDe } return foundation.EncodeI2PBase64(packet.V1.From.Bytes()), packet.V1.Payload, true case networking.DatagramProtocolDatagram2: - valid, err := packet.V2.VerifyTarget(s.endpoint.Hash()) + valid, err := packet.V2.VerifyTargetAt(s.endpoint.Hash(), uint32(time.Now().Unix())) if err != nil || !valid || packet.V2.From.Hash() != delivery.From { return "", nil, false } From 3593aa117eaf15c8839dca7f946e061f2c195e89 Mon Sep 17 00:00:00 2001 From: "qodo-code-review[bot]" <151058649+qodo-code-review[bot]@users.noreply.github.com> Date: Sat, 5 Sep 2026 03:29:36 +0000 Subject: [PATCH 3/5] fix: Clear transient signing keys --- foundation/address_generator.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/foundation/address_generator.go b/foundation/address_generator.go index 3781a5b..f0226d4 100644 --- a/foundation/address_generator.go +++ b/foundation/address_generator.go @@ -305,10 +305,13 @@ func (d *LocalDestination) Sign(message []byte) ([]byte, error) { if d.offline != nil { switch d.offline.keyType { case SigningEdDSASHA512Ed25519: - return ed25519.Sign(ed25519.NewKeyFromSeed(d.offline.private), message), nil + private := ed25519.NewKeyFromSeed(d.offline.private) + defer clear(private) + return ed25519.Sign(private, message), nil case SigningRedDSASHA512Ed25519: var private [32]byte copy(private[:], d.offline.private) + defer clear(private[:]) return Red25519Sign(private, message) default: return nil, ErrEncryptedSigningKey From e3e5fe675bab6ba7bd0b86846bf376d79630cde9 Mon Sep 17 00:00:00 2001 From: "qodo-code-review[bot]" <151058649+qodo-code-review[bot]@users.noreply.github.com> Date: Sat, 5 Sep 2026 03:29:43 +0000 Subject: [PATCH 4/5] fix: Support bounded offline signature serialization --- foundation/offline_signature.go | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/foundation/offline_signature.go b/foundation/offline_signature.go index 62b04ff..f62ade0 100644 --- a/foundation/offline_signature.go +++ b/foundation/offline_signature.go @@ -18,13 +18,24 @@ type OfflineSignature struct { Signature []byte } -// SignedContent returns the authorized content (expires, key type, public key) -// covered by the offline signature. +// SignedContentLen returns the encoded length of the authorized content. +func (o OfflineSignature) SignedContentLen() int { return 6 + len(o.PublicKey) } + +// MarshalSignedContentTo serializes the authorized content into dst. +func (o OfflineSignature) MarshalSignedContentTo(dst []byte) (int, error) { + if len(dst) < o.SignedContentLen() { + return 0, ErrInvalidIdentity + } + binary.BigEndian.PutUint32(dst[:4], o.Expires) + binary.BigEndian.PutUint16(dst[4:6], uint16(o.Type)) + copy(dst[6:], o.PublicKey) + return o.SignedContentLen(), nil +} + +// SignedContent returns the authorized content (expires, key type, public key). func (o OfflineSignature) SignedContent() []byte { - signed := make([]byte, 6+len(o.PublicKey)) - binary.BigEndian.PutUint32(signed[:4], o.Expires) - binary.BigEndian.PutUint16(signed[4:6], uint16(o.Type)) - copy(signed[6:], o.PublicKey) + signed := make([]byte, o.SignedContentLen()) + _, _ = o.MarshalSignedContentTo(signed) return signed } From b74210ed2a1d07ebc597347ec1c0e12bfda6ea66 Mon Sep 17 00:00:00 2001 From: "qodo-code-review[bot]" <151058649+qodo-code-review[bot]@users.noreply.github.com> Date: Sat, 5 Sep 2026 03:29:46 +0000 Subject: [PATCH 5/5] fix: Return offline parser views --- client/internal/sam/private_destination.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/internal/sam/private_destination.go b/client/internal/sam/private_destination.go index 0a56c7d..fdc0737 100644 --- a/client/internal/sam/private_destination.go +++ b/client/internal/sam/private_destination.go @@ -219,11 +219,11 @@ func parseOfflinePrivateKey(identity foundation.Identity, section []byte) (*offl return nil, ErrInvalidKey } offset := 6 - public := append([]byte(nil), section[offset:offset+publicLength]...) + public := section[offset : offset+publicLength] offset += publicLength - signature := append([]byte(nil), section[offset:offset+signatureLength]...) + signature := section[offset : offset+signatureLength] offset += signatureLength - transientPrivate := append([]byte(nil), section[offset:]...) + transientPrivate := section[offset:] return &offlinePrivateKey{ OfflineSignature: foundation.OfflineSignature{ Expires: binary.BigEndian.Uint32(section[:4]),