Repository: v2fly/v2ray-core
File: proxy/shadowsocks2022/udp_aes.go (Go)

Code before:

func (p *AESUDPClientPacketProcessor) DecodeUDPResp(input []byte, resp *UDPResponse,
	cache UDPClientPacketProcessorCachedStateContainer,
) error {
	separateHeaderBuffer := buf.New()
	defer separateHeaderBuffer.Release()
	{
		encryptedDest := separateHeaderBuffer.Extend(16)
		p.responseSeparateHeaderBlockCipher.Decrypt(encryptedDest, input)
	}
	separateHeaderStruct := separateHeader{}
	{
		err := struc.Unpack(separateHeaderBuffer, &separateHeaderStruct)
		if err != nil {
			return newError("failed to unpack separateHeader").Base(err)
		}
	}
	resp.PacketID = separateHeaderStruct.PacketID
	resp.SessionID = separateHeaderStruct.SessionID
	{
		// Since we need to decrypt the main packet to see client session id, we
		// have no way to know where should this key be found in the cache indexed
		// by client session id.
		// Luckily, V2Ray's implementation of shadowsocks2022-aes will always generate
		// server session id based on client session id to allow client map it back
		// to client session id.
		var generatedCacheBytes [8]byte
		copy(generatedCacheBytes[:], separateHeaderStruct.SessionID[:])
		generatedCacheBytes[7] = ^generatedCacheBytes[7]
		cacheKey := string(separateHeaderBuffer.Bytes()[0:8])

		receivedCacheInterface := cache.GetCachedState(cacheKey)
		cachedState := &cachedUDPState{}
		if receivedCacheInterface != nil {
			cachedState = receivedCacheInterface.(*cachedUDPState)
		}

		if cachedState.sessionRecvAEAD == nil {
			cachedState.sessionRecvAEAD = p.mainPacketAEAD(separateHeaderBuffer.Bytes()[0:8])
			cache.PutCachedState(cacheKey, cachedState)
		}

		mainPacketAEADMaterialized := cachedState.sessionRecvAEAD
		decryptedDestBuffer := buf.New()
		decryptedDest := decryptedDestBuffer.Extend(int32(len(input)) - 16 - int32(mainPacketAEADMaterialized.Overhead()))
		_, err := mainPacketAEADMaterialized.Open(decryptedDest[:0], separateHeaderBuffer.Bytes()[4:16], input[16:], nil)
		if err != nil {
			return newError("failed to open main packet").Base(err)
		}
		decryptedDestReader := bytes.NewReader(decryptedDest)
		headerStruct := respHeader{}

Review comment (suggestion):
The server session ID is random for a reason.

When a server restarts and receives packets from a client session that has been there since before the restart, it cannot restore the previous server session state, and must create a new server session.

If you use an algorithm that generate stable server session IDs from client session IDs, the new server session subkey will be the same as the one before restarting. But the nonce counter still starts from 0. This is nonce reuse with the same key. AES-GCM breaks catastrophically in this case.

Code after:

func (p *AESUDPClientPacketProcessor) DecodeUDPResp(input []byte, resp *UDPResponse,
	cache UDPClientPacketProcessorCachedStateContainer,
) error {
	separateHeaderBuffer := buf.New()
	defer separateHeaderBuffer.Release()
	{
		encryptedDest := separateHeaderBuffer.Extend(16)
		p.responseSeparateHeaderBlockCipher.Decrypt(encryptedDest, input)
	}
	separateHeaderStruct := separateHeader{}
	{
		err := struc.Unpack(separateHeaderBuffer, &separateHeaderStruct)
		if err != nil {
			return newError("failed to unpack separateHeader").Base(err)
		}
	}
	resp.PacketID = separateHeaderStruct.PacketID
	resp.SessionID = separateHeaderStruct.SessionID
	{
		cacheKey := string(separateHeaderBuffer.Bytes()[0:8])
		receivedCacheInterface := cache.GetCachedServerState(cacheKey)
		cachedState := &cachedUDPState{}
		if receivedCacheInterface != nil {
			cachedState = receivedCacheInterface.(*cachedUDPState)
		}

		if cachedState.sessionRecvAEAD == nil {
			cachedState.sessionRecvAEAD = p.mainPacketAEAD(separateHeaderBuffer.Bytes()[0:8])
			cache.PutCachedServerState(cacheKey, cachedState)
		}

		mainPacketAEADMaterialized := cachedState.sessionRecvAEAD
		decryptedDestBuffer := buf.New()
		decryptedDest := decryptedDestBuffer.Extend(int32(len(input)) - 16 - int32(mainPacketAEADMaterialized.Overhead()))
		_, err := mainPacketAEADMaterialized.Open(decryptedDest[:0], separateHeaderBuffer.Bytes()[4:16], input[16:], nil)
		if err != nil {
			return newError("failed to open main packet").Base(err)
		}
		decryptedDestReader := bytes.NewReader(decryptedDest)
		headerStruct := respHeader{}
		{
			err := struc.Unpack(decryptedDestReader, &headerStruct)
			if err != nil {
				return newError("failed to unpack header").Base(err)
			}
		}
		resp.TimeStamp = headerStruct.TimeStamp
		addressReaderBuf := buf.New()
		defer addressReaderBuf.Release()
		var port net.Port