// Copyright 2018 Google Inc. All Rights Reserved.
//
// 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 ctutil contains utilities for Certificate Transparency.
package ctutil

import (
	"bytes"
	"crypto"
	"crypto/sha256"
	"encoding/base64"
	"errors"
	"fmt"

	ct "github.com/google/certificate-transparency-go"
	"github.com/google/certificate-transparency-go/tls"
	"github.com/google/certificate-transparency-go/x509"
)

var emptyHash = [sha256.Size]byte{}

// LeafHashB64 does as LeafHash does, but returns the leaf hash base64-encoded.
// The base64-encoded leaf hash returned by B64LeafHash can be used with the
// get-proof-by-hash API endpoint of Certificate Transparency Logs.
func LeafHashB64(chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) (string, error) {
	hash, err := LeafHash(chain, sct, embedded)
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(hash[:]), nil
}

// LeafHash calculates the leaf hash of the certificate or precertificate at
// chain[0] that sct was issued for.
//
// sct is required because the SCT timestamp is used to calculate the leaf hash.
// Leaf hashes are unique to (pre)certificate-SCT pairs.
//
// This function can be used with three different types of leaf certificate:
//   - X.509 Certificate:
//       If using this function to calculate the leaf hash for a normal X.509
//       certificate then it is enough to just provide the end entity
//       certificate in chain. This case assumes that the SCT being provided is
//       not embedded within the leaf certificate provided, i.e. the certificate
//       is what was submitted to the Certificate Transparency Log in order to
//       obtain the SCT.  For this case, set embedded to false.
//   - Precertificate:
//       If using this function to calculate the leaf hash for a precertificate
//       then the issuing certificate must also be provided in chain.  The
//       precertificate should be at chain[0], and its issuer at chain[1].  For
//       this case, set embedded to false.
//   - X.509 Certificate containing the SCT embedded within it:
//       If using this function to calculate the leaf hash for a certificate
//       where the SCT provided is embedded within the certificate you
//       are providing at chain[0], set embedded to true.  LeafHash will
//       calculate the leaf hash by building the corresponding precertificate.
//       LeafHash will return an error if the provided SCT cannot be found
//       embedded within chain[0].  As with the precertificate case, the issuing
//       certificate must also be provided in chain.  The certificate containing
//       the embedded SCT should be at chain[0], and its issuer at chain[1].
//
// Note: LeafHash doesn't check that the provided SCT verifies for the given
// chain.  It simply calculates what the leaf hash would be for the given
// (pre)certificate-SCT pair.
func LeafHash(chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) ([sha256.Size]byte, error) {
	leaf, err := createLeaf(chain, sct, embedded)
	if err != nil {
		return emptyHash, err
	}
	return ct.LeafHashForLeaf(leaf)
}

// VerifySCT takes the public key of a Certificate Transparency Log, a
// certificate chain, and an SCT and verifies whether the SCT is a valid SCT for
// the certificate at chain[0], signed by the Log that the public key belongs
// to.  If the SCT does not verify, an error will be returned.
//
// This function can be used with three different types of leaf certificate:
//   - X.509 Certificate:
//       If using this function to verify an SCT for a normal X.509 certificate
//       then it is enough to just provide the end entity certificate in chain.
//       This case assumes that the SCT being provided is not embedded within
//       the leaf certificate provided, i.e. the certificate is what was
//       submitted to the Certificate Transparency Log in order to obtain the
//       SCT.  For this case, set embedded to false.
//   - Precertificate:
//       If using this function to verify an SCT for a precertificate then the
//       issuing certificate must also be provided in chain.  The precertificate
//       should be at chain[0], and its issuer at chain[1].  For this case, set
//       embedded to false.
//   - X.509 Certificate containing the SCT embedded within it:
//       If the SCT you wish to verify is embedded within the certificate you
//       are providing at chain[0], set embedded to true.  VerifySCT will
//       verify the provided SCT by building the corresponding precertificate.
//       VerifySCT will return an error if the provided SCT cannot be found
//       embedded within chain[0].  As with the precertificate case, the issuing
//       certificate must also be provided in chain.  The certificate containing
//       the embedded SCT should be at chain[0], and its issuer at chain[1].
func VerifySCT(pubKey crypto.PublicKey, chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) error {
	leaf, err := createLeaf(chain, sct, embedded)
	if err != nil {
		return err
	}

	s, err := ct.NewSignatureVerifier(pubKey)
	if err != nil {
		return fmt.Errorf("error creating signature verifier: %s", err)
	}

	entry := ct.LogEntry{Leaf: *leaf}
	return s.VerifySCTSignature(*sct, entry)
}

func createLeaf(chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) (*ct.MerkleTreeLeaf, error) {
	if len(chain) == 0 {
		return nil, errors.New("chain is empty")
	}
	if sct == nil {
		return nil, errors.New("sct is nil")
	}

	if embedded {
		sctPresent, err := ContainsSCT(chain[0], sct)
		if err != nil {
			return nil, fmt.Errorf("error checking for SCT in leaf certificate: %s", err)
		}
		if !sctPresent {
			return nil, errors.New("SCT provided is not embedded within leaf certificate")
		}
	}

	certType := ct.X509LogEntryType
	if chain[0].IsPrecertificate() || embedded {
		certType = ct.PrecertLogEntryType
	}

	var leaf *ct.MerkleTreeLeaf
	var err error
	if embedded {
		leaf, err = ct.MerkleTreeLeafForEmbeddedSCT(chain, sct.Timestamp)
	} else {
		leaf, err = ct.MerkleTreeLeafFromChain(chain, certType, sct.Timestamp)
	}
	if err != nil {
		return nil, fmt.Errorf("error creating MerkleTreeLeaf: %s", err)
	}
	return leaf, nil
}

// ContainsSCT checks to see whether the given SCT is embedded within the given
// certificate.
func ContainsSCT(cert *x509.Certificate, sct *ct.SignedCertificateTimestamp) (bool, error) {
	if cert == nil || sct == nil {
		return false, nil
	}

	sctBytes, err := tls.Marshal(*sct)
	if err != nil {
		return false, fmt.Errorf("error tls.Marshalling SCT: %s", err)
	}
	for _, s := range cert.SCTList.SCTList {
		if bytes.Equal(sctBytes, s.Val) {
			return true, nil
		}
	}
	return false, nil
}
