// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

package subprocess

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"strings"
)

// The following structures reflect the JSON of ACVP PBKDF tests. See
// https://pages.nist.gov/ACVP/draft-celi-acvp-pbkdf.html

type pbkdf struct{}

type pbkdfVectorSet struct {
	Groups []pbkdfTestGroup `json:"testGroups"`
}

type pbkdfTestGroup struct {
	ID      uint64 `json:"tgId"`
	HmacAlg string `json:"hmacAlg"`
	Type    string `json:"testType"`
	Tests   []struct {
		ID         uint64 `json:"tcId"`
		KeyLen     uint64 `json:"keyLen"`
		SaltHex    string `json:"salt"`
		Pwd        string `json:"password"`
		Iterations uint64 `json:"iterationCount"`
	} `json:"tests"`
}

type pbkdfTestGroupResponse struct {
	ID    uint64              `json:"tgId"`
	Tests []pbkdfTestResponse `json:"tests"`
}

type pbkdfTestResponse struct {
	ID            uint64 `json:"tcId"`
	DerivedKeyHex string `json:"derivedKey"`
}

// List of supported HMAC Algorithms by NIST
var HmacAlgList map[string]bool = map[string]bool{
	"SHA-1":        true,
	"SHA2-224":     true,
	"SHA2-256":     true,
	"SHA2-384":     true,
	"SHA2-512":     true,
	"SHA2-512/224": true,
	"SHA2-512/256": true,
}

// Key and salt length mins and maxes as defined by NIST
var keyLenMin uint64 = 112
var keyLenMax uint64 = 4096
var iterationMin uint64 = 1
var iterationMax uint64 = 10000000
var saltLenMin int = 128
var saltLenMax int = 4096
var passwordLenMin int = 8
var passwordLenMax int = 4096

// List of supported PBKDF test types by NIST
var TestTypeList map[string]bool = map[string]bool{
	"AFT": true,
}

func (a *pbkdf) Process(vectorSet []byte, m Transactable) (interface{}, error) {
	var parsed pbkdfVectorSet
	if err := json.Unmarshal(vectorSet, &parsed); err != nil {
		return nil, err
	}

	var ret []pbkdfTestGroupResponse
	// See draft-celi-acvp-symmetric.html#table-6. (NIST no longer publish HTML
	// versions of the ACVP documents. You can find fragments in
	// https://github.com/usnistgov/ACVP.)
	for _, group := range parsed.Groups {
		response := pbkdfTestGroupResponse{
			ID: group.ID,
		}

		// Check for valid HMAC Algorithm
		_, algIn := HmacAlgList[group.HmacAlg]
		if !algIn {
			return nil, fmt.Errorf("test group %d has unsupported HMAC algorithm %q", group.ID, group.HmacAlg)
		}

		// Check if the test type is valid
		_, typeIn := TestTypeList[group.Type]
		if !typeIn {
			return nil, fmt.Errorf("test group %d has unsupported test type %q", group.ID, group.Type)
		}

		// Check each test in the test group
		for _, test := range group.Tests {

			if test.KeyLen > keyLenMax || test.KeyLen < keyLenMin {
				return nil, fmt.Errorf("test case %d/%d requests output key of length %d, but expected output key length between %d and %d", group.ID, test.ID, test.KeyLen, keyLenMin, keyLenMax)
			}

			// Check length of salt against NIST min/max
			if len(test.SaltHex)*4 > saltLenMax || len(test.SaltHex)*4 < saltLenMin {
				return nil, fmt.Errorf("test case %d/%d contains salt %q of bit length %d, but expected bit length between %d and %d", group.ID, test.ID, test.SaltHex, len(test.SaltHex)*4, saltLenMin, saltLenMax)
			}

			if len(test.Pwd) > passwordLenMax || len(test.Pwd) < passwordLenMin {
				return nil, fmt.Errorf("test case %d/%d contains password %s of length %d, but expected password length between %d and %d", group.ID, test.ID, test.Pwd, len(test.Pwd), passwordLenMin, passwordLenMax)
			}

			if test.Iterations > iterationMax || test.Iterations < iterationMin {
				return nil, fmt.Errorf("test case %d/%d contains iteration count of %d, but expected iteration count between %d and %d", group.ID, test.ID, test.Iterations, iterationMin, iterationMax)
			}

			testResp := pbkdfTestResponse{ID: test.ID}

			salt, salt_err := hex.DecodeString(test.SaltHex)
			if salt_err != nil {
				return nil, fmt.Errorf("Salt decode failed: %s", salt_err)
			}

			var result [][]uint8
			result, err := m.Transact("PBKDF", 1, []byte(test.Pwd),
				salt, uint32le(uint32(test.Iterations)), []byte(group.HmacAlg),
				uint32le(uint32(test.KeyLen)))
			if err != nil {
				return nil, fmt.Errorf("PBKDF operation failed: %s", err)
			}

			testResp.DerivedKeyHex = strings.ToUpper(hex.EncodeToString(result[0]))
			response.Tests = append(response.Tests, testResp)
		}

		ret = append(ret, response)
	}

	return ret, nil
}
