/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/
/*
Notice: This file has been modified for Hyperledger Fabric SDK Go usage.
Please review third_party pinning scripts and patches for more details.
*/

package msp

import (
	"bytes"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/asn1"
	cspx509 "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric/bccsp/x509"
	"math/big"
	"reflect"
	"strings"
	"time"

	"github.com/pkg/errors"
)

func (msp *bccspmsp) validateIdentity(id *identity) error {
	id.validationMutex.Lock()
	defer id.validationMutex.Unlock()

	// return cached validation value if already validated
	if id.validated {
		return id.validationErr
	}

	id.validated = true

	validationChain, err := msp.getCertificationChainForBCCSPIdentity(id)
	if err != nil {
		id.validationErr = errors.WithMessage(err, "could not obtain certification chain")
		return id.validationErr
	}

	err = msp.validateIdentityAgainstChain(id, validationChain)
	if err != nil {
		id.validationErr = errors.WithMessage(err, "could not validate identity against certification chain")
		return id.validationErr
	}

	err = msp.internalValidateIdentityOusFunc(id)
	if err != nil {
		id.validationErr = errors.WithMessage(err, "could not validate identity's OUs")
		return id.validationErr
	}

	err = msp.validateIdentityCN(id)
	if err != nil {
		return errors.WithMessage(err, "could not validate identity's CommonName")
	}

	return nil
}

func (msp *bccspmsp) validateCAIdentity(id *identity) error {
	if !id.cert.IsCA {
		return errors.New("Only CA identities can be validated")
	}

	caOps := msp.getValidityOptsForCert(id.cert)
	keyUsages := make([]x509.ExtKeyUsage, 1)
	keyUsages[0] = x509.ExtKeyUsageAny
	caOps.KeyUsages = keyUsages

	validationChain, err := msp.getUniqueValidationChain(id.cert, caOps)
	//validationChain, err := msp.getUniqueValidationChain(id.cert, msp.getValidityOptsForCert(id.cert))
	if err != nil {
		return errors.WithMessage(err, "could not obtain certification chain")
	}
	if len(validationChain) == 1 {
		// validationChain[0] is the root CA certificate
		return nil
	}

	return msp.validateIdentityAgainstChain(id, validationChain)
}

func (msp *bccspmsp) validateTLSCAIdentity(cert *x509.Certificate, opts *cspx509.VerifyOptions) error {
	if !cert.IsCA {
		return errors.New("Only CA identities can be validated")
	}

	validationChain, err := msp.getUniqueValidationChain(cert, *opts)
	if err != nil {
		return errors.WithMessage(err, "could not obtain certification chain")
	}
	if len(validationChain) == 1 {
		// validationChain[0] is the root CA certificate
		return nil
	}

	return msp.validateCertAgainstChain(cert, validationChain)
}

func (msp *bccspmsp) validateIdentityAgainstChain(id *identity, validationChain []*x509.Certificate) error {
	return msp.validateCertAgainstChain(id.cert, validationChain)
}

func (msp *bccspmsp) validateCertAgainstChain(cert *x509.Certificate, validationChain []*x509.Certificate) error {
	// here we know that the identity is valid; now we have to check whether it has been revoked

	// identify the SKI of the CA that signed this cert
	SKI, err := getSubjectKeyIdentifierFromCert(validationChain[1])
	if err != nil {
		return errors.WithMessage(err, "could not obtain Subject Key Identifier for signer cert")
	}

	// check whether one of the CRLs we have has this cert's
	// SKI as its AuthorityKeyIdentifier
	for _, crl := range msp.CRL {
		aki, err := getAuthorityKeyIdentifierFromCrl(crl)
		if err != nil {
			return errors.WithMessage(err, "could not obtain Authority Key Identifier for crl")
		}

		// check if the SKI of the cert that signed us matches the AKI of any of the CRLs
		if bytes.Equal(aki, SKI) {
			// we have a CRL, check whether the serial number is revoked
			for _, rc := range crl.TBSCertList.RevokedCertificates {
				if rc.SerialNumber.Cmp(cert.SerialNumber) == 0 {
					// We have found a CRL whose AKI matches the SKI of
					// the CA (root or intermediate) that signed the
					// certificate that is under validation. As a
					// precaution, we verify that said CA is also the
					// signer of this CRL.
					err = validationChain[1].CheckCRLSignature(crl)
					if err != nil {
						// the CA cert that signed the certificate
						// that is under validation did not sign the
						// candidate CRL - skip
						mspLogger.Warningf("Invalid signature over the identified CRL, error %+v", err)
						continue
					}

					// A CRL also includes a time of revocation so that
					// the CA can say "this cert is to be revoked starting
					// from this time"; however here we just assume that
					// revocation applies instantaneously from the time
					// the MSP config is committed and used so we will not
					// make use of that field
					return errors.New("The certificate has been revoked")
				}
			}
		}
	}

	for _, rcert := range msp.revocationCert {
		if (rcert.SerialNumber.Cmp(cert.SerialNumber)) == 0 {
			mspLogger.Warningf("The certificate has been revoked,serialnum: %v", rcert.SerialNumber)
			return errors.New("The certificate has been revoked")
		}
	}

	return nil
}

func (msp *bccspmsp) validateIdentityOUsV1(id *identity) error {
	// Check that the identity's OUs are compatible with those recognized by this MSP,
	// meaning that the intersection is not empty.
	if len(msp.ouIdentifiers) > 0 {
		found := false

		for _, OU := range id.GetOrganizationalUnits() {
			certificationIDs, exists := msp.ouIdentifiers[OU.OrganizationalUnitIdentifier]

			if exists {
				for _, certificationID := range certificationIDs {
					if bytes.Equal(certificationID, OU.CertifiersIdentifier) {
						found = true
						break
					}
				}
			}
		}

		if !found {
			if len(id.GetOrganizationalUnits()) == 0 {
				return errors.New("the identity certificate does not contain an Organizational Unit (OU)")
			}
			return errors.Errorf("none of the identity's organizational units %s are in MSP %s", OUIDs(id.GetOrganizationalUnits()), msp.name)
		}
	}

	return nil
}

func (msp *bccspmsp) validateIdentityOUsV11(id *identity) error {
	// Run the same checks as per V1
	err := msp.validateIdentityOUsV1(id)
	if err != nil {
		return err
	}

	// Perform V1_1 additional checks:
	//
	// -- Check for OU enforcement
	if !msp.ouEnforcement {
		// No enforcement required
		return nil
	}

	// Make sure that the identity has only one of the special OUs
	// used to tell apart clients or peers.
	counter := 0
	for _, OU := range id.GetOrganizationalUnits() {
		// Is OU.OrganizationalUnitIdentifier one of the special OUs?
		var nodeOU *OUIdentifier
		switch OU.OrganizationalUnitIdentifier {
		case msp.clientOU.OrganizationalUnitIdentifier:
			nodeOU = msp.clientOU
		case msp.peerOU.OrganizationalUnitIdentifier:
			nodeOU = msp.peerOU
		default:
			continue
		}

		// Yes. Then, enforce the certifiers identifier is this is specified.
		// It is not specified, it means that any certification path is fine.
		if len(nodeOU.CertifiersIdentifier) != 0 && !bytes.Equal(nodeOU.CertifiersIdentifier, OU.CertifiersIdentifier) {
			return errors.Errorf("certifiersIdentifier does not match: %v, MSP: [%s]", OUIDs(id.GetOrganizationalUnits()), msp.name)
		}
		counter++
		if counter > 1 {
			break
		}
	}

	// the identity should have exactly one OU role, return an error if the counter is not 1.
	if counter == 0 {
		return errors.Errorf("the identity does not have an OU that resolves to client or peer. OUs: %s, MSP: [%s]", OUIDs(id.GetOrganizationalUnits()), msp.name)
	}
	if counter > 1 {
		return errors.Errorf("the identity must be a client or a peer identity to be valid, not a combination of them. OUs: %s, MSP: [%s]", OUIDs(id.GetOrganizationalUnits()), msp.name)
	}

	return nil
}

func (msp *bccspmsp) validateIdentityOUsV142(id *identity) error {
	// Run the same checks as per V1
	err := msp.validateIdentityOUsV1(id)
	if err != nil {
		return err
	}

	// -- Check for OU enforcement
	if !msp.ouEnforcement {
		// No enforcement required
		return nil
	}

	// Make sure that the identity has only one of the special OUs
	// used to tell apart clients, peers and admins.
	counter := 0
	validOUs := make(map[string]*OUIdentifier)
	if msp.clientOU != nil {
		validOUs[msp.clientOU.OrganizationalUnitIdentifier] = msp.clientOU
	}
	if msp.peerOU != nil {
		validOUs[msp.peerOU.OrganizationalUnitIdentifier] = msp.peerOU
	}
	if msp.adminOU != nil {
		validOUs[msp.adminOU.OrganizationalUnitIdentifier] = msp.adminOU
	}
	if msp.ordererOU != nil {
		validOUs[msp.ordererOU.OrganizationalUnitIdentifier] = msp.ordererOU
	}

	for _, OU := range id.GetOrganizationalUnits() {
		// Is OU.OrganizationalUnitIdentifier one of the special OUs?
		nodeOU := validOUs[OU.OrganizationalUnitIdentifier]
		if nodeOU == nil {
			continue
		}

		// Yes. Then, enforce the certifiers identifier in this is specified.
		// If is not specified, it means that any certification path is fine.
		if len(nodeOU.CertifiersIdentifier) != 0 && !bytes.Equal(nodeOU.CertifiersIdentifier, OU.CertifiersIdentifier) {
			return errors.Errorf("certifiersIdentifier does not match: %s, MSP: [%s]", OUIDs(id.GetOrganizationalUnits()), msp.name)
		}
		counter++
		if counter > 1 {
			break
		}
	}

	// the identity should have exactly one OU role, return an error if the counter is not 1.
	if counter == 0 {
		return errors.Errorf("the identity does not have an OU that resolves to client, peer, orderer, or admin role. OUs: %s, MSP: [%s]", OUIDs(id.GetOrganizationalUnits()), msp.name)
	}
	if counter > 1 {
		return errors.Errorf("the identity must have a client, a peer, an orderer, or an admin OU role to be valid, not a combination of them. OUs: %s, MSP: [%s]", OUIDs(id.GetOrganizationalUnits()), msp.name)
	}

	return nil
}

func (msp *bccspmsp) getValidityOptsForCert(cert *x509.Certificate) cspx509.VerifyOptions {
	// First copy the opts to override the CurrentTime field
	// in order to make the certificate passing the expiration test
	// independently from the real local current time.
	// This is a temporary workaround for FAB-3678

	var tempOpts cspx509.VerifyOptions
	tempOpts.Roots = msp.opts.Roots
	tempOpts.DNSName = msp.opts.DNSName
	tempOpts.Intermediates = msp.opts.Intermediates
	tempOpts.KeyUsages = msp.opts.KeyUsages
	tempOpts.CurrentTime = cert.NotBefore.Add(time.Second)

	return tempOpts
}

/*
   This is the definition of the ASN.1 marshalling of AuthorityKeyIdentifier
   from https://www.ietf.org/rfc/rfc5280.txt

   AuthorityKeyIdentifier ::= SEQUENCE {
      keyIdentifier             [0] KeyIdentifier           OPTIONAL,
      authorityCertIssuer       [1] GeneralNames            OPTIONAL,
      authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL  }

   KeyIdentifier ::= OCTET STRING

   CertificateSerialNumber  ::=  INTEGER

*/

type authorityKeyIdentifier struct {
	KeyIdentifier             []byte  `asn1:"optional,tag:0"`
	AuthorityCertIssuer       []byte  `asn1:"optional,tag:1"`
	AuthorityCertSerialNumber big.Int `asn1:"optional,tag:2"`
}

// getAuthorityKeyIdentifierFromCrl returns the Authority Key Identifier
// for the supplied CRL. The authority key identifier can be used to identify
// the public key corresponding to the private key which was used to sign the CRL.
func getAuthorityKeyIdentifierFromCrl(crl *pkix.CertificateList) ([]byte, error) {
	aki := authorityKeyIdentifier{}

	for _, ext := range crl.TBSCertList.Extensions {
		// Authority Key Identifier is identified by the following ASN.1 tag
		// authorityKeyIdentifier (2 5 29 35) (see https://tools.ietf.org/html/rfc3280.html)
		if reflect.DeepEqual(ext.Id, asn1.ObjectIdentifier{2, 5, 29, 35}) {
			_, err := asn1.Unmarshal(ext.Value, &aki)
			if err != nil {
				return nil, errors.Wrap(err, "failed to unmarshal AKI")
			}

			return aki.KeyIdentifier, nil
		}
	}

	return nil, errors.New("authorityKeyIdentifier not found in certificate")
}

// getSubjectKeyIdentifierFromCert returns the Subject Key Identifier for the supplied certificate
// Subject Key Identifier is an identifier of the public key of this certificate
func getSubjectKeyIdentifierFromCert(cert *x509.Certificate) ([]byte, error) {
	var SKI []byte

	for _, ext := range cert.Extensions {
		// Subject Key Identifier is identified by the following ASN.1 tag
		// subjectKeyIdentifier (2 5 29 14) (see https://tools.ietf.org/html/rfc3280.html)
		if reflect.DeepEqual(ext.Id, asn1.ObjectIdentifier{2, 5, 29, 14}) {
			_, err := asn1.Unmarshal(ext.Value, &SKI)
			if err != nil {
				return nil, errors.Wrap(err, "failed to unmarshal Subject Key Identifier")
			}

			return SKI, nil
		}
	}

	return nil, errors.New("subjectKeyIdentifier not found in certificate")
}

// isCACert does a few checks on the certificate,
// assuming it's a CA; it returns true if all looks good
// and false otherwise
func isCACert(cert *x509.Certificate) bool {
	_, err := getSubjectKeyIdentifierFromCert(cert)
	if err != nil {
		return false
	}

	if !cert.IsCA {
		return false
	}

	return true
}

//modify by rayytwang at 2018?? 05?? 14?? ????? 10:19:30 CST
func (msp *bccspmsp) validateIdentityCN(id *identity) error {

	//TODO: 1)add issuer cn control;2)add sign verify with cfca root

	if len(id.cert.Issuer.Organization) < 1 {
		return errors.New("the identity certificate does not contain an issuerOrg")
	}

	issuerOrg := id.cert.Issuer.Organization[0]
	if len(issuerOrg) == 0 {
		return errors.New("the identity certificate does not contain an issuerOrg")
	}

	//if issuer cn not queal cfca root, skip cn check
	if strings.Compare(issuerOrg, "China Financial Certification Authority") != 0 {
		return nil
	}

	issuer := id.cert.Issuer.CommonName
	if len(issuer) == 0 {
		return errors.New("the identity certificate does not contain an issuer")
	}

	//cn format:  orderer@domain,type=orderer
	cn := id.cert.Subject.CommonName
	if len(cn) == 0 {
		return errors.New("the identity certificate does not contain an cn")
	}

	//msp format: orgMsp@domain
	mspId := id.GetMSPIdentifier()
	if len(mspId) == 0 {
		return errors.New("the msp identity is nil")
	}

	mspLogger.Infof("issuer[%s] cn[%s] mspid[%s]", issuer, cn, mspId)

	//get type from cn
	/*
	   orderer??????:
	   Admin@domain@client
	   orderer@domain@orderer
	   peer??????:
	   Admin@org1.domain@client
	   peer0@org1.domain@=peer

	   CN=051@Admin@org2.example.com@client@12345467@2
	*/

	seps := strings.Split(cn, "@")
	if len(seps) != 6 {
		return errors.New("missing domain info.the identity certificate not signed by cfca")
	}

	var buf bytes.Buffer
	var domain, org string

	certType := seps[3]
	tmpSeps := strings.Split(seps[2], ".")

	// org and domain only support [a-zA-Z]
	if len(tmpSeps) == 1 {
		org = "orderer"
		domain = tmpSeps[0]
	} else if len(tmpSeps) == 2 {
		org = tmpSeps[0]
		domain = tmpSeps[1]
	} else {
		return errors.New("missing org and domain info.the identity certificate not signed by cfca")
	}

	if len(domain) < 0 || len(org) < 0 {
		return errors.New("missing doman or org info.the identity certificate not signed by cfca")
	}

	suffix := "-" + domain

	switch certType {
	case "client":

		if org == "orderer" {
			buf.WriteString("OrdererMSP")
			if strings.HasSuffix(mspId, suffix) {
				buf.WriteString("-")
				buf.WriteString(domain)
			}
		} else {
			buf.WriteString(org)
			buf.WriteString("MSP-")
			buf.WriteString(domain)
		}
	case "peer":
		buf.WriteString(org)
		buf.WriteString("MSP-")
		buf.WriteString(domain)
	case "orderer":
		if org != "orderer" {
			return errors.New("missing orderer info.the identity certificate not signed by cfca")
		}

		buf.WriteString("OrdererMSP")
		if strings.HasSuffix(mspId, suffix) {
			buf.WriteString("-")
			buf.WriteString(domain)
		}
	// tls cert
	/*
		case "server-tls":
			fallthrough
		case "client-tls":
			return nil
	*/
	default:
		return errors.New("the identity certificate not signed by cfca")
	}

	cnMspId := buf.String()
	cnMspId = strings.ToLower(cnMspId)
	lowerMspId := strings.ToLower(mspId)

	if strings.Compare(lowerMspId, cnMspId) != 0 {
		mspLogger.Errorf("mspId[%s] cnMspId[%s]", mspId, cnMspId)
		return errors.Errorf("the identity certificate common name dismatch with mspid %s <--> %s", mspId, cnMspId)
	}

	return nil
}
