package client import ( "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha1" "crypto/sha256" "crypto/sha512" "encoding/base64" "encoding/binary" "encoding/hex" "errors" "fmt" "log" "math/big" "net" "time" // Unused but placeholder "go-ts/pkg/protocol" "go-ts/pkg/transport" "filippo.io/edwards25519" ) // HandshakeState tracks the progress of the connection initialization. type HandshakeState struct { Step int // Init Cookies/Buffers A0 [4]byte A1 [16]byte A2 [100]byte // Puzzle Data X *big.Int N *big.Int Level uint32 // Identity IdentityKey *ecdsa.PrivateKey Alpha []byte // 10 random bytes // Server Data Beta []byte Omega []byte // Server Public Key (DER) License []byte // Crypto ClientEkPub [32]byte ClientEkPriv [32]byte ClientScalar *edwards25519.Scalar // Client ephemeral private key (scalar) SharedSecret []byte SharedIV []byte SharedMac []byte IdentityOffset uint64 // Extracted/Mined offset IdentityLevel int Conn *transport.TS3Conn } func NewHandshakeState(conn *transport.TS3Conn) (*HandshakeState, error) { // Generate Identity On Startup (or load from disk in future) privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, fmt.Errorf("failed to generate identity key: %v", err) } alpha := make([]byte, 10) if _, err := rand.Read(alpha); err != nil { return nil, fmt.Errorf("failed to generate alpha: %v", err) } // Generate Client Ephemeral Key (TS3 Style) // TS3 uses a random 32-byte scalar with the high bit masked (&= 0x7F). // It does NOT use standard Ed25519 clamping (bits 0-2 cleared, bit 254 set). var clientScalar *edwards25519.Scalar var clientKeyBytes [32]byte for { if _, err := rand.Read(clientKeyBytes[:]); err != nil { return nil, err } clientKeyBytes[31] &= 0x7F // Mask high bit (positive scalar) // Try to load as scalar. Might fail if >= L (very unlikely) clientScalar, err = new(edwards25519.Scalar).SetCanonicalBytes(clientKeyBytes[:]) if err == nil { break } } return &HandshakeState{ Step: 0, Conn: conn, IdentityKey: privKey, Alpha: alpha, ClientScalar: clientScalar, }, nil } // ... SendPacket0, HandlePacket1, SendPacket2, HandlePacket3 logic unchanged ... // (Omitting for brevity if replacing, but user asked for full file usually. // Just including necessary parts and new methods) func (h *HandshakeState) SendPacket0() error { buf := new(bytes.Buffer) ts := int32(time.Now().Unix()) - 1356998400 binary.Write(buf, binary.BigEndian, ts) buf.WriteByte(0x00) // Step 0 now := int32(time.Now().Unix()) binary.Write(buf, binary.BigEndian, now) rand.Read(h.A0[:]) buf.Write(h.A0[:]) buf.Write(make([]byte, 8)) // 8 Zeros pkt := protocol.NewPacket(protocol.PacketTypeInit1, buf.Bytes()) pkt.Header.PacketID = 101 pkt.Header.ClientID = 0 pkt.Header.MAC = protocol.HandshakeMac return h.Conn.SendPacket(pkt) } func (h *HandshakeState) HandlePacket1(pkt *protocol.Packet) error { if len(pkt.Data) < 21 { return fmt.Errorf("packet 1 too short") } if pkt.Data[0] != 0x01 { return fmt.Errorf("expected step 1, got %d", pkt.Data[0]) } copy(h.A1[:], pkt.Data[1:17]) h.Step = 1 return nil } func (h *HandshakeState) SendPacket2() error { buf := new(bytes.Buffer) ts := int32(time.Now().Unix()) - 1356998400 binary.Write(buf, binary.BigEndian, ts) buf.WriteByte(0x02) // Step 2 buf.Write(h.A1[:]) a0Rev := [4]byte{h.A0[3], h.A0[2], h.A0[1], h.A0[0]} buf.Write(a0Rev[:]) pkt := protocol.NewPacket(protocol.PacketTypeInit1, buf.Bytes()) pkt.Header.PacketID = 102 pkt.Header.MAC = protocol.HandshakeMac return h.Conn.SendPacket(pkt) } func (h *HandshakeState) HandlePacket3(pkt *protocol.Packet) error { if len(pkt.Data) < 233 { return fmt.Errorf("packet 3 too short") } if pkt.Data[0] != 0x03 { return fmt.Errorf("expected step 3, got %d", pkt.Data[0]) } h.X = new(big.Int).SetBytes(pkt.Data[1:65]) h.N = new(big.Int).SetBytes(pkt.Data[65:129]) h.Level = binary.BigEndian.Uint32(pkt.Data[129:133]) copy(h.A2[:], pkt.Data[133:233]) h.Step = 3 log.Printf("Received Puzzle: Level=%d", h.Level) return h.SendPacket4() } func (h *HandshakeState) SendPacket4() error { e := new(big.Int).Lsh(big.NewInt(1), uint(h.Level)) y := new(big.Int).Exp(h.X, e, h.N) yBytes := y.Bytes() yPadded := make([]byte, 64) if len(yBytes) > 64 { copy(yPadded, yBytes[len(yBytes)-64:]) } else { copy(yPadded[64-len(yBytes):], yBytes) } buf := new(bytes.Buffer) ts := int32(time.Now().Unix()) - 1356998400 binary.Write(buf, binary.BigEndian, ts) buf.WriteByte(0x04) // Step 4 xPadded := make([]byte, 64) xb := h.X.Bytes() if len(xb) > 64 { copy(xPadded, xb[len(xb)-64:]) } else { copy(xPadded[64-len(xb):], xb) } buf.Write(xPadded) nPadded := make([]byte, 64) nb := h.N.Bytes() if len(nb) > 64 { copy(nPadded, nb[len(nb)-64:]) } else { copy(nPadded[64-len(nb):], nb) } buf.Write(nPadded) binary.Write(buf, binary.BigEndian, h.Level) buf.Write(h.A2[:]) buf.Write(yPadded) cmdData := h.GenerateClientInitIV() buf.Write([]byte(cmdData)) pkt := protocol.NewPacket(protocol.PacketTypeInit1, buf.Bytes()) pkt.Header.PacketID = 103 pkt.Header.MAC = protocol.HandshakeMac log.Println("Sending Packet 4 with Solution and clientinitiv...") return h.Conn.SendPacket(pkt) } // ProcessInitivexpand2 handles the decrypted command logic func (h *HandshakeState) ProcessInitivexpand2(cmdArgs map[string]string) error { if h.Step >= 5 { log.Println("Ignoring duplicate initivexpand2 (Step already advanced)") return nil } lStr, ok := cmdArgs["l"] if !ok { return errors.New("missing license (l)") } lData, err := base64.StdEncoding.DecodeString(lStr) if err != nil { return err } betaStr, ok := cmdArgs["beta"] if !ok { return errors.New("missing beta") } h.Beta, err = base64.StdEncoding.DecodeString(betaStr) if err != nil { return err } // 1. Derive Server Public Key (Edwards Y) serverEdPubBytes, err := protocol.ParseLicenseAndDeriveKey(lData) if err != nil { return fmt.Errorf("LICENSE FAIL: %v", err) } // Load Server Public Key as Edwards Point serverPoint, err := new(edwards25519.Point).SetBytes(serverEdPubBytes) if err != nil { return fmt.Errorf("invalid server public key point: %v", err) } // 2. Client Ephemeral Key (Scalar) is pre-generated in NewHandshakeState (h.ClientScalar) // Compute Client Public Key (Point) = Scalar * Base clientPubPoint := new(edwards25519.Point).ScalarBaseMult(h.ClientScalar) h.ClientEkPub = [32]byte(clientPubPoint.Bytes()) // 3. Calculate Shared Secret (Ed25519 Scalar Mult) // Negate Server Public Key (TS3/Punisher.NaCl logic) serverPointNeg := new(edwards25519.Point).Negate(serverPoint) // Multiply: Result = Scalar * (-ServerPoint) sharedPoint := new(edwards25519.Point).ScalarMult(h.ClientScalar, serverPointNeg) sharedBytes := sharedPoint.Bytes() // XOR the last byte with 0x80 (Flip sign bit of X coordinate) sharedBytes[31] ^= 0x80 // 4. SHA512 Hash of the result hash := sha512.Sum512(sharedBytes) h.SharedSecret = hash[:] h.SharedIV = make([]byte, 64) copy(h.SharedIV, h.SharedSecret) // XOR operations // SharedIV[0..10] xor alpha // SharedIV[10..64] xor beta // Alpha is 10 bytes (h.Alpha) for i := 0; i < 10; i++ { h.SharedIV[i] ^= h.Alpha[i] } // Beta should be 54 bytes log.Printf("Debug - Beta Length: %d", len(h.Beta)) if len(h.Beta) >= 54 { for i := 0; i < 54; i++ { h.SharedIV[10+i] ^= h.Beta[i] } } // SharedMac = SHA1(SharedIV)[0..8] macHash := sha1.Sum(h.SharedIV) h.SharedMac = make([]byte, 8) copy(h.SharedMac, macHash[0:8]) log.Printf("Debug - SharedSecret (SHA512): %s", hex.EncodeToString(h.SharedSecret)) log.Printf("Debug - SharedIV: %s", hex.EncodeToString(h.SharedIV)) log.Printf("Debug - SharedMac: %s", hex.EncodeToString(h.SharedMac[:])) log.Println("Shared Secret & Keys Calculated using TS3 Ed25519 logic.") return h.SendClientEk() } func (h *HandshakeState) SendClientEk() error { // clientek ek={ek} proof={proof} // ek = base64(client_public_key) [Ed25519 Compressed Point] ekStr := base64.StdEncoding.EncodeToString(h.ClientEkPub[:]) // proof = base64(ecdsa_sign(client_public_key + beta)) // "sign must be done with the private key from the identity keypair." (P-256) proofBuf := append(h.ClientEkPub[:], h.Beta...) hash := sha256.Sum256(proofBuf) r, s, err := ecdsa.Sign(rand.Reader, h.IdentityKey, hash[:]) if err != nil { return err } // Encode Signature (ASN.1 DER) // Reverting to DER as server uses DER. sigContent := new(bytes.Buffer) writeBigInt(sigContent, r) writeBigInt(sigContent, s) sigSeq := new(bytes.Buffer) sigSeq.WriteByte(0x30) writeLength(sigSeq, sigContent.Len()) sigSeq.Write(sigContent.Bytes()) proofBytes := sigSeq.Bytes() proofStr := base64.StdEncoding.EncodeToString(proofBytes) log.Printf("Debug - ClientEk: %s", ekStr) log.Printf("Debug - Proof (DER): %s", hex.EncodeToString(proofBytes)) // Construct Command cmd := fmt.Sprintf("clientek ek=%s proof=%s", protocol.Escape(ekStr), protocol.Escape(proofStr)) // Packet 1 (New counter logic? Spec: "clientek already has the packet id 1") // This is the START of the new encrypted session? // "The normal packet id counting starts with this packet." buf := new(bytes.Buffer) buf.Write([]byte(cmd)) pkt := protocol.NewPacket(protocol.PacketTypeCommand, buf.Bytes()) pkt.Header.PacketID = 1 // Reset to 1 pkt.Header.SetType(protocol.PacketTypeCommand) // Ensure flag (NewProtocol? Unencrypted?) // Encryption? // "All Command ... Packets must get encrypted." (Using OLD Handshake keys? Or NEW?) // "The crypto handshake is now completed. The normal encryption scheme ... is from now on used." // Usually implies clientek IS encrypted with the NEW keys. // Add PacketFlagNewProtocol pkt.Header.Type |= protocol.PacketFlagNewProtocol // 0x20 // Encryption // Try using HandshakeKey like initivexpand2 instead of SharedSecret // The crypto switch might happen AFTER clientek is accepted key := protocol.HandshakeKey nonce := protocol.HandshakeNonce // Prepare Meta for EAX // Meta for Client->Server: PID(2) + CID(2) + PT(1) = 5 bytes meta := make([]byte, 5) binary.BigEndian.PutUint16(meta[0:2], pkt.Header.PacketID) binary.BigEndian.PutUint16(meta[2:4], pkt.Header.ClientID) meta[4] = pkt.Header.Type // Encrypt cipherDict, mac, err := protocol.EncryptEAX(key, nonce, meta, pkt.Data) if err != nil { return err } pkt.Data = cipherDict copy(pkt.Header.MAC[:], mac) log.Println("Sending clientek (Packet 1) [Encrypted]") h.Step = 5 return h.Conn.SendPacket(pkt) } func (h *HandshakeState) GenerateClientInitIV() string { // ... (Existing implementation) ... // Copy from previous step alphaStr := base64.StdEncoding.EncodeToString(h.Alpha) omegaStr := h.GenerateOmega() ip := "127.0.0.1" if addr, ok := h.Conn.RemoteAddr().(*net.UDPAddr); ok { ip = addr.IP.String() } else if h.Conn.RemoteAddr() != nil { ip = h.Conn.RemoteAddr().String() if host, _, err := net.SplitHostPort(ip); err == nil { ip = host } } return fmt.Sprintf("clientinitiv alpha=%s omega=%s ot=1 ip=%s", protocol.Escape(alphaStr), protocol.Escape(omegaStr), protocol.Escape(ip)) } func (h *HandshakeState) GenerateOmega() string { // ... (Existing implementation) ... pub := h.IdentityKey.PublicKey content := new(bytes.Buffer) content.Write([]byte{0x03, 0x02, 0x07, 0x00}) content.Write([]byte{0x02, 0x01, 0x20}) writeBigInt(content, pub.X) writeBigInt(content, pub.Y) seq := new(bytes.Buffer) seq.WriteByte(0x30) writeLength(seq, content.Len()) seq.Write(content.Bytes()) return base64.StdEncoding.EncodeToString(seq.Bytes()) } func writeBigInt(buf *bytes.Buffer, n *big.Int) { b := n.Bytes() buf.WriteByte(0x02) padded := b if len(b) > 0 && b[0] >= 0x80 { padded = make([]byte, len(b)+1) padded[0] = 0x00 copy(padded[1:], b) } else if len(b) == 0 { padded = []byte{0x00} } writeLength(buf, len(padded)) buf.Write(padded) } func writeLength(buf *bytes.Buffer, length int) { if length < 128 { buf.WriteByte(byte(length)) } else { s := fmt.Sprintf("%x", length) if len(s)%2 != 0 { s = "0" + s } b, _ := hex.DecodeString(s) buf.WriteByte(0x80 | byte(len(b))) buf.Write(b) } } // ImproveSecurityLevel mines a counter to achieve the target security level. func (h *HandshakeState) ImproveSecurityLevel(targetLevel int) { omega := h.GenerateOmega() // Base64 of ASN.1 Public Key // Start from current offset (usually 0) counter := h.IdentityOffset log.Printf("Mining Identity Level %d... ", targetLevel) for { // Construct data: Omega + Counter (ASCII) data := fmt.Sprintf("%s%d", omega, counter) // SHA1 hash := sha1.Sum([]byte(data)) // Count leading zero bits zeros := countLeadingZeros(hash[:]) if zeros >= targetLevel { h.IdentityLevel = zeros h.IdentityOffset = counter log.Printf("Found! Offset=%d, Level=%d\n", counter, zeros) return } counter++ if counter%100000 == 0 { // fmt.Print(".") } } } func countLeadingZeros(hash []byte) int { zeros := 0 for _, b := range hash { if b == 0 { zeros += 8 } else { // Count bits in this byte for i := 7; i >= 0; i-- { if (b>>i)&1 == 0 { zeros++ } else { return zeros } } return zeros } } return zeros }