// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package hpke

import (
	"bytes"
	"math/rand"
	"testing"

	"github.com/tink-crypto/tink-go/v2/subtle/random"
	"github.com/tink-crypto/tink-go/v2/subtle"
	pb "github.com/tink-crypto/tink-go/v2/proto/hpke_go_proto"
)

func TestNewEncryptDecryptUnknownKEM(t *testing.T) {
	params := validParams(t)
	params.Kem = pb.HpkeKem_KEM_UNKNOWN
	pubKey, privKey := pubPrivKeys(t, params)

	if _, err := NewEncrypt(pubKey); err == nil {
		t.Error("NewEncrypt(unknown KEM): got success, want err")
	}
	if _, err := NewDecrypt(privKey); err == nil {
		t.Error("NewDecrypt(unknown KEM): got success, want err")
	}
}

func TestNewEncryptDecryptUnknownKDF(t *testing.T) {
	params := validParams(t)
	params.Kdf = pb.HpkeKdf_KDF_UNKNOWN
	pubKey, privKey := pubPrivKeys(t, params)

	if _, err := NewEncrypt(pubKey); err == nil {
		t.Error("NewEncrypt(unknown KDF): got success, want err")
	}
	if _, err := NewDecrypt(privKey); err == nil {
		t.Error("NewDecrypt(unknown KDF): got success, want err")
	}
}

func TestNewEncryptDecryptUnknownAEAD(t *testing.T) {
	params := validParams(t)
	params.Aead = pb.HpkeAead_AEAD_UNKNOWN
	pubKey, privKey := pubPrivKeys(t, params)

	if _, err := NewEncrypt(pubKey); err == nil {
		t.Error("NewEncrypt(unknown AEAD): got success, want err")
	}
	if _, err := NewDecrypt(privKey); err == nil {
		t.Error("NewDecrypt(unknown AEAD): got success, want err")
	}
}

func TestNewEncryptDecryptMissingParams(t *testing.T) {
	pubKey, privKey := pubPrivKeys(t, nil)

	if _, err := NewEncrypt(pubKey); err == nil {
		t.Error("NewEncrypt(missing params): got success, want err")
	}
	if _, err := NewDecrypt(privKey); err == nil {
		t.Error("NewDecrypt(missing params): got success, want err")
	}
}

func TestNewEncryptMissingPubKeyBytes(t *testing.T) {
	pubKey, _ := pubPrivKeys(t, validParams(t))
	pubKey.PublicKey = nil
	if _, err := NewEncrypt(pubKey); err == nil {
		t.Error("NewEncrypt(nil pub key): got success, want err")
	}
	pubKey.PublicKey = []byte{}
	if _, err := NewEncrypt(pubKey); err == nil {
		t.Error("NewEncrypt(zero-length pub key): got success, want err")
	}
}

func TestNewDecryptMissingPrivKeyBytes(t *testing.T) {
	_, privKey := pubPrivKeys(t, validParams(t))
	privKey.PrivateKey = nil
	if _, err := NewDecrypt(privKey); err == nil {
		t.Error("NewDecrypt(nil priv key): got success, want err")
	}
	privKey.PrivateKey = []byte{}
	if _, err := NewDecrypt(privKey); err == nil {
		t.Error("NewDecrypt(zero-length priv key): got success, want err")
	}
}

func TestEncryptDecrypt(t *testing.T) {
	aeadIDs := []pb.HpkeAead{pb.HpkeAead_AES_128_GCM, pb.HpkeAead_AES_256_GCM, pb.HpkeAead_CHACHA20_POLY1305}
	for _, aeadID := range aeadIDs {
		params := &pb.HpkeParams{
			Kem:  pb.HpkeKem_DHKEM_X25519_HKDF_SHA256,
			Kdf:  pb.HpkeKdf_HKDF_SHA256,
			Aead: aeadID,
		}
		pubKey, privKey := pubPrivKeys(t, params)

		enc, err := NewEncrypt(pubKey)
		if err != nil {
			t.Fatalf("NewEncrypt: err %q", err)
		}
		dec, err := NewDecrypt(privKey)
		if err != nil {
			t.Fatalf("NewDecrypt: err %q", err)
		}

		wantPT := random.GetRandomBytes(200)
		ctxInfo := random.GetRandomBytes(100)
		ct, err := enc.Encrypt(wantPT, ctxInfo)
		if err != nil {
			t.Fatalf("Encrypt: err %q", err)
		}
		gotPT, err := dec.Decrypt(ct, ctxInfo)
		if err != nil {
			t.Fatalf("Decrypt: err %q", err)
		}
		if !bytes.Equal(gotPT, wantPT) {
			t.Errorf("Decrypt: got %q, want %q", gotPT, wantPT)
		}
	}
}

func TestDecryptModifiedCiphertextOrContextInfo(t *testing.T) {
	pubKey, privKey := pubPrivKeys(t, validParams(t))
	enc, err := NewEncrypt(pubKey)
	if err != nil {
		t.Fatalf("NewEncrypt: err %q", err)
	}
	dec, err := NewDecrypt(privKey)
	if err != nil {
		t.Fatalf("NewDecrypt: err %q", err)
	}

	wantPT := random.GetRandomBytes(200)
	ctxInfo := random.GetRandomBytes(100)
	ct, err := enc.Encrypt(wantPT, ctxInfo)
	if err != nil {
		t.Fatalf("Encrypt: err %q", err)
	}
	gotPT, err := dec.Decrypt(ct, ctxInfo)
	if err != nil {
		t.Fatalf("Decrypt: err %q", err)
	}
	if !bytes.Equal(gotPT, wantPT) {
		t.Errorf("Decrypt: got %q, want %q", gotPT, wantPT)
	}

	tests := []struct {
		name    string
		ct      []byte
		ctxInfo []byte
	}{
		{"extended ct", append(ct, []byte("hi there")...), ctxInfo},
		{"flip byte ct", flipRandByte(t, ct), ctxInfo},
		{"short ct", ct[:len(ct)-5], ctxInfo},
		{"empty ct", []byte{}, ctxInfo},
		{"extended ctxInfo", ct, append(ctxInfo, []byte("hi there")...)},
		{"flip byte ctxInfo", ct, flipRandByte(t, ctxInfo)},
		{"short ctxInfo", ct, ctxInfo[:len(ctxInfo)-5]},
		{"empty ctxInfo", ct, []byte{}},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			if _, err := dec.Decrypt(test.ct, test.ctxInfo); err == nil {
				t.Error("Decrypt: got success, want err")
			}
		})
	}
}

func TestEncryptDecryptEmptyContextInfo(t *testing.T) {
	pubKey, privKey := pubPrivKeys(t, validParams(t))
	enc, err := NewEncrypt(pubKey)
	if err != nil {
		t.Fatalf("NewEncrypt: err %q", err)
	}
	dec, err := NewDecrypt(privKey)
	if err != nil {
		t.Fatalf("NewDecrypt: err %q", err)
	}

	wantPT := random.GetRandomBytes(200)
	ctxInfo := []byte{}
	ct, err := enc.Encrypt(wantPT, ctxInfo)
	if err != nil {
		t.Fatalf("Encrypt: err %q", err)
	}
	gotPT, err := dec.Decrypt(ct, ctxInfo)
	if err != nil {
		t.Fatalf("Decrypt: err %q", err)
	}
	if !bytes.Equal(gotPT, wantPT) {
		t.Errorf("Decrypt: got %q, want %q", gotPT, wantPT)
	}
}

// TestDecryptEncapsulatedKeyWithFlippedMSB checks that ciphertexts with its
// encapsulated key MSB flipped fails to decrypt. See details at b/213886185.
func TestDecryptEncapsulatedKeyWithFlippedMSB(t *testing.T) {
	pubKey, privKey := pubPrivKeys(t, validParams(t))
	enc, err := NewEncrypt(pubKey)
	if err != nil {
		t.Fatalf("NewEncrypt: err %q", err)
	}
	dec, err := NewDecrypt(privKey)
	if err != nil {
		t.Fatalf("NewDecrypt: err %q", err)
	}

	wantPT := random.GetRandomBytes(200)
	ctxInfo := random.GetRandomBytes(100)
	ct, err := enc.Encrypt(wantPT, ctxInfo)
	if err != nil {
		t.Fatalf("Encrypt: err %q", err)
	}
	gotPT, err := dec.Decrypt(ct, ctxInfo)
	if err != nil {
		t.Fatalf("Decrypt: err %q", err)
	}
	if !bytes.Equal(gotPT, wantPT) {
		t.Errorf("Decrypt: got %q, want %q", gotPT, wantPT)
	}

	// Flip the MSB of the encapsulated key, which is the first 32 bytes of ct.
	ct[31] = ct[31] ^ 128
	if _, err := dec.Decrypt(ct, ctxInfo); err == nil {
		t.Error("Decrypt with encapsulated key MSB flipped: got success, want err")
	}
}

func validParams(t *testing.T) *pb.HpkeParams {
	t.Helper()
	return &pb.HpkeParams{
		Kem:  pb.HpkeKem_DHKEM_X25519_HKDF_SHA256,
		Kdf:  pb.HpkeKdf_HKDF_SHA256,
		Aead: pb.HpkeAead_AES_256_GCM,
	}
}

func pubPrivKeys(t *testing.T, params *pb.HpkeParams) (*pb.HpkePublicKey, *pb.HpkePrivateKey) {
	t.Helper()

	priv, err := subtle.GeneratePrivateKeyX25519()
	if err != nil {
		t.Fatalf("GeneratePrivateKeyX25519: err %q", err)
	}
	pub, err := subtle.PublicFromPrivateX25519(priv)
	if err != nil {
		t.Fatalf("PublicFromPrivateX25519: err %q", err)
	}

	pubKey := &pb.HpkePublicKey{
		Version:   0,
		Params:    params,
		PublicKey: pub,
	}
	privKey := &pb.HpkePrivateKey{
		Version:    0,
		PublicKey:  pubKey,
		PrivateKey: priv,
	}
	return pubKey, privKey
}

func flipRandByte(t *testing.T, b []byte) []byte {
	t.Helper()
	ret := make([]byte, len(b))
	copy(ret, b)
	randByte := rand.Intn(len(b))
	ret[randByte] = ret[randByte] ^ 255
	return ret
}
