// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pkcs12

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/asn1"
	"testing"
)

func TestRoundTripPkcs8Rsa(t *testing.T) {
	privateKey, err := rsa.GenerateKey(rand.Reader, 512)
	if err != nil {
		t.Fatalf("failed to generate a private key: %s", err)
	}

	bytes, err := marshalPKCS8PrivateKey(privateKey)
	if err != nil {
		t.Fatalf("failed to marshal private key: %s", err)
	}

	key, err := x509.ParsePKCS8PrivateKey(bytes)
	if err != nil {
		t.Fatalf("failed to parse private key: %s", err)
	}

	actualPrivateKey, ok := key.(*rsa.PrivateKey)
	if !ok {
		t.Fatalf("expected key to be of type *rsa.PrivateKey, but actual was %T", key)
	}

	if actualPrivateKey.Validate() != nil {
		t.Fatalf("private key did not validate")
	}

	if actualPrivateKey.N.Cmp(privateKey.N) != 0 {
		t.Errorf("private key's N did not round trip")
	}
	if actualPrivateKey.D.Cmp(privateKey.D) != 0 {
		t.Errorf("private key's D did not round trip")
	}
	if actualPrivateKey.E != privateKey.E {
		t.Errorf("private key's E did not round trip")
	}
	if actualPrivateKey.Primes[0].Cmp(privateKey.Primes[0]) != 0 {
		t.Errorf("private key's P did not round trip")
	}
	if actualPrivateKey.Primes[1].Cmp(privateKey.Primes[1]) != 0 {
		t.Errorf("private key's Q did not round trip")
	}
}

func TestRoundTripPkcs8Ecdsa(t *testing.T) {
	privateKey, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
	if err != nil {
		t.Fatalf("failed to generate a private key: %s", err)
	}

	bytes, err := marshalPKCS8PrivateKey(privateKey)
	if err != nil {
		t.Fatalf("failed to marshal private key: %s", err)
	}

	key, err := x509.ParsePKCS8PrivateKey(bytes)
	if err != nil {
		t.Fatalf("failed to parse private key: %s", err)
	}

	actualPrivateKey, ok := key.(*ecdsa.PrivateKey)
	if !ok {
		t.Fatalf("expected key to be of type *ecdsa.PrivateKey, but actual was %T", key)
	}

	// sanity check, not exhaustive
	if actualPrivateKey.D.Cmp(privateKey.D) != 0 {
		t.Errorf("private key's D did not round trip")
	}
	if actualPrivateKey.X.Cmp(privateKey.X) != 0 {
		t.Errorf("private key's X did not round trip")
	}
	if actualPrivateKey.Y.Cmp(privateKey.Y) != 0 {
		t.Errorf("private key's Y did not round trip")
	}
	if actualPrivateKey.Curve.Params().B.Cmp(privateKey.Curve.Params().B) != 0 {
		t.Errorf("private key's Curve.B did not round trip")
	}
}

func TestNullParametersPkcs8Rsa(t *testing.T) {
	privateKey, err := rsa.GenerateKey(rand.Reader, 512)
	if err != nil {
		t.Fatalf("failed to generate a private key: %s", err)
	}

	checkNullParameter(t, privateKey)
}

func TestNullParametersPkcs8Ecdsa(t *testing.T) {
	privateKey, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
	if err != nil {
		t.Fatalf("failed to generate a private key: %s", err)
	}

	checkNullParameter(t, privateKey)
}

func checkNullParameter(t *testing.T, privateKey interface{}) {
	bytes, err := marshalPKCS8PrivateKey(privateKey)
	if err != nil {
		t.Fatalf("failed to marshal private key: %s", err)
	}

	var pkcs pkcs8
	rest, err := asn1.Unmarshal(bytes, &pkcs)
	if err != nil {
		t.Fatalf("failed to unmarshal PKCS#8: %s", err)
	}

	if len(rest) != 0 {
		t.Fatalf("unexpected trailing bytes of len=%d, bytes=%x", len(rest), rest)
	}

	// Only version == 0 is known and valid
	if pkcs.Version != 0 {
		t.Errorf("expected version=0, but actual=%d", pkcs.Version)
	}

	// ensure a NULL parameter is inserted
	if pkcs.Algo.Parameters.Tag != 5 {
		t.Errorf("expected parameters to be NULL, but actual tag=%d, class=%d, isCompound=%t, bytes=%x",
			pkcs.Algo.Parameters.Tag,
			pkcs.Algo.Parameters.Class,
			pkcs.Algo.Parameters.IsCompound,
			pkcs.Algo.Parameters.Bytes)
	}
}
