// Copyright 2016, 2017 Thales e-Security, Inc
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

package crypto11

import (
	"crypto"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	_ "crypto/sha1"
	_ "crypto/sha256"
	_ "crypto/sha512"
	"testing"

	"github.com/miekg/pkcs11"

	"github.com/stretchr/testify/assert"

	"github.com/stretchr/testify/require"
)

var curves = []elliptic.Curve{
	elliptic.P224(),
	elliptic.P256(),
	elliptic.P384(),
	elliptic.P521(),
	// plus something with explicit parameters
}

func TestNativeECDSA(t *testing.T) {
	var err error
	var key *ecdsa.PrivateKey
	for _, curve := range curves {
		if key, err = ecdsa.GenerateKey(curve, rand.Reader); err != nil {
			t.Errorf("crypto.ecdsa.GenerateKey: %v", err)
			return
		}
		testEcdsaSigning(t, key, crypto.SHA1, curve.Params().Name, "SHA-1")
		testEcdsaSigning(t, key, crypto.SHA224, curve.Params().Name, "SHA-224")
		testEcdsaSigning(t, key, crypto.SHA256, curve.Params().Name, "SHA-256")
		testEcdsaSigning(t, key, crypto.SHA384, curve.Params().Name, "SHA-384")
		testEcdsaSigning(t, key, crypto.SHA512, curve.Params().Name, "SHA-512")
	}
}

func TestHardECDSA(t *testing.T) {
	ctx, err := ConfigureFromFile("config")
	require.NoError(t, err)

	defer func() {
		err = ctx.Close()
		require.NoError(t, err)
	}()

	for _, curve := range curves {
		id := randomBytes()
		label := randomBytes()

		key, err := ctx.GenerateECDSAKeyPairWithLabel(id, label, curve)
		require.NoError(t, err)
		require.NotNil(t, key)
		defer func(k Signer) { _ = k.Delete() }(key)

		testEcdsaSigning(t, key, crypto.SHA1, curve.Params().Name, "SHA-1")
		testEcdsaSigning(t, key, crypto.SHA224, curve.Params().Name, "SHA-224")
		testEcdsaSigning(t, key, crypto.SHA256, curve.Params().Name, "SHA-256")
		testEcdsaSigning(t, key, crypto.SHA384, curve.Params().Name, "SHA-384")
		testEcdsaSigning(t, key, crypto.SHA512, curve.Params().Name, "SHA-512")

		key2, err := ctx.FindKeyPair(id, nil)
		require.NoError(t, err)
		testEcdsaSigning(t, key2.(*pkcs11PrivateKeyECDSA), crypto.SHA256, curve.Params().Name, "SHA-256")

		key3, err := ctx.FindKeyPair(nil, label)
		require.NoError(t, err)
		testEcdsaSigning(t, key3.(crypto.Signer), crypto.SHA384, curve.Params().Name, "SHA-384")
	}
}

func testEcdsaSigning(t *testing.T, key crypto.Signer, hashFunction crypto.Hash, curveName, hashName string) {

	plaintext := []byte("sign me with ECDSA")
	h := hashFunction.New()
	_, err := h.Write(plaintext)
	require.NoError(t, err)
	plaintextHash := h.Sum(nil)

	sigDER, err := key.Sign(rand.Reader, plaintextHash, nil)

	p11Err, ok := err.(pkcs11.Error)
	if ok && p11Err == pkcs11.CKR_KEY_SIZE_RANGE {
		// Returned by CloudHSM (at least), for key sizes it doesn't support.
		t.Logf("Skipping unsupported curve %s and hash %s", curveName, hashName)
		return
	}

	assert.NoErrorf(t, err, "Sign failed for curve %s and hash %s", curveName, hashName)
	if err != nil {
		// We assert and return, so that errors are more informative over a range of curves
		// and hashes.
		return
	}

	var sig dsaSignature
	err = sig.unmarshalDER(sigDER)
	require.NoError(t, err)

	ecdsaPubkey := key.Public().(crypto.PublicKey).(*ecdsa.PublicKey)
	if !ecdsa.Verify(ecdsaPubkey, plaintextHash, sig.R, sig.S) {
		t.Errorf("ECDSA Verify (hash %v): %v", hashFunction, err)
	}

}

func TestEcdsaRequiredArgs(t *testing.T) {
	ctx, err := ConfigureFromFile("config")
	require.NoError(t, err)

	defer func() {
		require.NoError(t, ctx.Close())
	}()

	_, err = ctx.GenerateECDSAKeyPair(nil, elliptic.P224())
	require.Error(t, err)

	val := randomBytes()

	_, err = ctx.GenerateECDSAKeyPairWithLabel(nil, val, elliptic.P224())
	require.Error(t, err)

	_, err = ctx.GenerateECDSAKeyPairWithLabel(val, nil, elliptic.P224())
	require.Error(t, err)
}
