// 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))
}
