//
// Copyright (c) SAS Institute Inc.
//
// 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 appmanifest

import (
	"bytes"
	"crypto"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rsa"
	"encoding/binary"
	"encoding/hex"
	"errors"
	"math/big"

	"github.com/sassoftware/relic/lib/certloader"
	"github.com/sassoftware/relic/lib/x509tools"
)

func PublisherIdentity(cert *certloader.Certificate) (string, string, error) {
	issuer := cert.Issuer()
	if issuer == nil {
		return "", "", errors.New("unable to find issuer certificate in chain")
	}
	aki, err := x509tools.SubjectKeyID(issuer.PublicKey)
	if err != nil {
		return "", "", err
	}
	name := x509tools.FormatPkixName(cert.Leaf.RawSubject, x509tools.NameStyleMsOsco)
	return name, hex.EncodeToString(aki), nil
}

const (
	snkRsaPub     = 0x06
	snkRsaPriv    = 0x07
	snkRsaVersion = 0x02
	// https://msdn.microsoft.com/en-us/library/windows/desktop/aa375549(v=vs.85).aspx
	calgRsaSign = 0x2400 // CALG_RSA_SIGN
	calgSha1    = 0x8004 // CALG_SHA1
	calgEcdsa   = 0x2203 // CALG_ECDSA
	// bcrypt.h
	bcryptRsaPubMagic  = 0x31415352 // BCRYPT_RSAPUBLIC_MAGIC
	bcryptEcdsaPubP256 = 0x31534345 // BCRYPT_ECDSA_PUBLIC_P256_MAGIC
	bcryptEcdsaPubP384 = 0x33534345 // BCRYPT_ECDSA_PUBLIC_P384_MAGIC
	bcryptEcdsaPubP521 = 0x35534345 // BCRYPT_ECDSA_PUBLIC_P521_MAGIC
)

type snkHeader struct {
	PubAlgorithm  uint32
	HashAlgorithm uint32
	BlobSize      uint32
	KeyType       uint8
	Version       uint8
	Reserved      uint16
	PubAlgorithm2 uint32
}

type blobRsaPub struct {
	snkHeader
	// BCRYPT_RSAKEY_BLOB
	KeyMagic    uint32
	BitLength   uint32
	PubExponent uint32
} // N [BitLength/8]byte

type blobEcdsaPub struct {
	snkHeader
	// BCRYPT_ECCKEY_BLOB
	KeyMagic   uint32
	ByteLength uint32
} // X, Y [ByteLength]byte

// Calculate the publicKeyToken from a public key. This involves mangling it
// into a .snk file format, then hashing it.
//
// http://www.developerfusion.com/article/84422/the-key-to-strong-names/
func PublicKeyToken(pubKey crypto.PublicKey) (string, error) {
	snk, err := PublicKeyToSnk(pubKey)
	if err != nil {
		return "", err
	}
	d := crypto.SHA1.New()
	d.Write(snk)
	// token is the low 64 bits of sum decoded as a little-endian number, or in
	// other words the last 8 bytes in reverse order
	sum := d.Sum(nil)
	token := make([]byte, 8)
	for i := 0; i < 8; i++ {
		token[i] = sum[19-i]
	}
	return hex.EncodeToString(token), nil
}

// Convert public key to "snk" format
func PublicKeyToSnk(pubKey crypto.PublicKey) ([]byte, error) {
	var buf bytes.Buffer
	switch k := pubKey.(type) {
	case *rsa.PublicKey:
		modulus := bigIntToLE(k.N)
		if err := binary.Write(&buf, binary.LittleEndian, blobRsaPub{
			snkHeader: snkHeader{
				PubAlgorithm:  calgRsaSign,
				HashAlgorithm: calgSha1,
				BlobSize:      uint32(20 + len(modulus)),
				KeyType:       snkRsaPub,
				Version:       snkRsaVersion,
				PubAlgorithm2: calgRsaSign,
			},
			KeyMagic:    bcryptRsaPubMagic,
			BitLength:   uint32(8 * len(modulus)),
			PubExponent: uint32(k.E),
		}); err != nil {
			return nil, nil
		}
		buf.Write(modulus)
	case *ecdsa.PublicKey:
		// TODO: This is a best guess based on piecing together various
		// Microsoft documentation and header values, but some pieces are still
		// missing. ECDSA isn't supported for strong name signing, and
		// calcuating the publicKeyToken for SN is the only reason this
		// function is even here.
		var keyMagic uint32
		switch k.Curve {
		case elliptic.P256():
			keyMagic = bcryptEcdsaPubP256
		case elliptic.P384():
			keyMagic = bcryptEcdsaPubP384
		case elliptic.P521():
			keyMagic = bcryptEcdsaPubP521
		default:
			return nil, errors.New("unsupported ECDSA curve")
		}
		// TODO: are these supposed to be big-endian? the documentation for
		// BCRYPT_ECCKEY_BLOB says so, but it also said that about the RSA one
		// and yet the SNK format actually uses little endian...
		x := k.X.Bytes()
		y := k.Y.Bytes()
		if err := binary.Write(&buf, binary.LittleEndian, blobEcdsaPub{
			snkHeader: snkHeader{
				PubAlgorithm:  calgEcdsa,
				HashAlgorithm: calgSha1,
				BlobSize:      uint32(12 + 2*len(x)),
				KeyType:       snkRsaPub,     // TODO
				Version:       snkRsaVersion, // TODO
				PubAlgorithm2: calgEcdsa,
			},
			KeyMagic:   keyMagic,
			ByteLength: uint32(len(x)),
		}); err != nil {
			return nil, nil
		}
		buf.Write(x)
		buf.Write(y)
	default:
		return nil, errors.New("unsupported key type for strong name signing")
	}
	return buf.Bytes(), nil
}

func bigIntToLE(x *big.Int) []byte {
	b := x.Bytes()
	for i := 0; i < len(b)/2; i++ {
		j := len(b) - i - 1
		b[i], b[j] = b[j], b[i]
	}
	return b
}
