initial commit: standalone SoftEther VPN client in Go
Built-in DHCP (raw Ethernet frames through tunnel), automatic reconnection, host route management, classless static routes (option 121/249), DNS config. Single static binary, Linux only. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
829ca73b1b
340 changed files with 199140 additions and 0 deletions
137
pkg/client/crypto.go
Normal file
137
pkg/client/crypto.go
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
// SHA-0 implementation for SoftEther VPN password authentication.
|
||||
//
|
||||
// SoftEther uses SHA-0 (the original, withdrawn FIPS-180 hash — NOT SHA-1) for
|
||||
// password hashing and the SecurePassword challenge-response. SHA-0 differs from
|
||||
// SHA-1 only in that the message schedule expansion does NOT include a left-rotate
|
||||
// by 1 bit (see transform() below).
|
||||
//
|
||||
// Auth flow:
|
||||
// 1. Client hashes the plaintext password: HashedPassword = SHA0(password)
|
||||
// 2. Server sends a 20-byte random challenge in the Hello pack
|
||||
// 3. Client computes: SecurePassword = SHA0(HashedPassword + ServerRandom)
|
||||
// 4. Client sends SecurePassword in the auth pack
|
||||
//
|
||||
// Reference C implementation:
|
||||
// https://github.com/SoftEtherVPN/SoftEtherVPN/blob/v5.02.5187/src/Mayaqua/Encrypt.c#L1088 (Sha0)
|
||||
// https://github.com/SoftEtherVPN/SoftEtherVPN/blob/v5.02.5187/src/Cedar/Sam.c#L105 (SecurePassword)
|
||||
package client
|
||||
|
||||
const sha1Size = 20
|
||||
|
||||
func rol(bits int, value uint32) uint32 {
|
||||
return (value << bits) | (value >> (32 - bits))
|
||||
}
|
||||
|
||||
type sha0ctx struct {
|
||||
count uint64
|
||||
buf [64]byte
|
||||
state [8]uint32
|
||||
}
|
||||
|
||||
func (c *sha0ctx) init() {
|
||||
c.state[0] = 0x67452301
|
||||
c.state[1] = 0xEFCDAB89
|
||||
c.state[2] = 0x98BADCFE
|
||||
c.state[3] = 0x10325476
|
||||
c.state[4] = 0xC3D2E1F0
|
||||
c.count = 0
|
||||
}
|
||||
|
||||
func (c *sha0ctx) transform() {
|
||||
var W [80]uint32
|
||||
|
||||
p := 0
|
||||
t := 0
|
||||
for ; t < 16; t++ {
|
||||
W[t] = uint32(c.buf[p])<<24 | uint32(c.buf[p+1])<<16 | uint32(c.buf[p+2])<<8 | uint32(c.buf[p+3])
|
||||
p += 4
|
||||
}
|
||||
// SHA-0: W[t] = W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16] (NO rotate)
|
||||
// SHA-1 would be: W[t] = rol(1, W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16])
|
||||
for ; t < 80; t++ {
|
||||
W[t] = W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]
|
||||
}
|
||||
|
||||
A, B, C, D, E := c.state[0], c.state[1], c.state[2], c.state[3], c.state[4]
|
||||
for t = 0; t < 80; t++ {
|
||||
tmp := rol(5, A) + E + W[t]
|
||||
if t < 20 {
|
||||
tmp += (D ^ (B & (C ^ D))) + 0x5A827999
|
||||
} else if t < 40 {
|
||||
tmp += (B ^ C ^ D) + 0x6ED9EBA1
|
||||
} else if t < 60 {
|
||||
tmp += ((B & C) | (D & (B | C))) + 0x8F1BBCDC
|
||||
} else {
|
||||
tmp += (B ^ C ^ D) + 0xCA62C1D6
|
||||
}
|
||||
E = D
|
||||
D = C
|
||||
C = rol(30, B)
|
||||
B = A
|
||||
A = tmp
|
||||
}
|
||||
|
||||
c.state[0] += A
|
||||
c.state[1] += B
|
||||
c.state[2] += C
|
||||
c.state[3] += D
|
||||
c.state[4] += E
|
||||
}
|
||||
|
||||
func (c *sha0ctx) update(data []byte) {
|
||||
i := int(c.count & 63)
|
||||
c.count += uint64(len(data))
|
||||
for _, d := range data {
|
||||
c.buf[i] = d
|
||||
i++
|
||||
if i == 64 {
|
||||
c.transform()
|
||||
i = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *sha0ctx) final() {
|
||||
cnt := c.count * 8
|
||||
c.update([]byte{0x80})
|
||||
for c.count&63 != 56 {
|
||||
c.update([]byte{0x0})
|
||||
}
|
||||
for i := 0; i < 8; i++ {
|
||||
c.update([]byte{byte(cnt >> ((7 - i) * 8))})
|
||||
}
|
||||
p := 0
|
||||
for i := 0; i < 5; i++ {
|
||||
c.buf[p] = byte(c.state[i] >> 24)
|
||||
c.buf[p+1] = byte(c.state[i] >> 16)
|
||||
c.buf[p+2] = byte(c.state[i] >> 8)
|
||||
c.buf[p+3] = byte(c.state[i])
|
||||
p += 4
|
||||
}
|
||||
}
|
||||
|
||||
func sha0(data []byte) [sha1Size]byte {
|
||||
var c sha0ctx
|
||||
c.init()
|
||||
c.update(data)
|
||||
c.final()
|
||||
var out [sha1Size]byte
|
||||
copy(out[:], c.buf[:sha1Size])
|
||||
return out
|
||||
}
|
||||
|
||||
// SecurePassword computes the challenge-response: SHA0(HashedPassword + ServerRandom).
|
||||
// See: https://github.com/SoftEtherVPN/SoftEtherVPN/blob/v5.02.5187/src/Cedar/Sam.c#L105
|
||||
func SecurePassword(hashedPassword, random [sha1Size]byte) [sha1Size]byte {
|
||||
buf := make([]byte, sha1Size*2)
|
||||
copy(buf, hashedPassword[:])
|
||||
copy(buf[sha1Size:], random[:])
|
||||
return sha0(buf)
|
||||
}
|
||||
|
||||
// HashPassword computes SHA0(password) — the first step of SoftEther password auth.
|
||||
// The result is what SoftEther stores server-side as "HashedPassword".
|
||||
// See: https://github.com/SoftEtherVPN/SoftEtherVPN/blob/v5.02.5187/src/Cedar/Sam.c#L91 (HashPassword)
|
||||
func HashPassword(password string) [sha1Size]byte {
|
||||
return sha0([]byte(password))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue