// Copyright 2014 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 scanner

import (
	"context"
	"log"
	"math/big"
	"regexp"
	"time"

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

// Matcher describes how to match certificates and precertificates, based solely on the parsed [pre-]certificate;
// clients should implement this interface to perform their own match criteria.
type Matcher interface {
	// CertificateMatches is called by the scanner for each X509 Certificate found in the log.
	// The implementation should return true if the passed Certificate is interesting, and false otherwise.
	CertificateMatches(*x509.Certificate) bool

	// PrecertificateMatches is called by the scanner for each CT Precertificate found in the log.
	// The implementation should return true if the passed Precertificate is interesting, and false otherwise.
	PrecertificateMatches(*ct.Precertificate) bool
}

// MatchAll is a Matcher which will match every possible Certificate and Precertificate.
type MatchAll struct{}

// CertificateMatches returns true if the given cert should match; in this case, always.
func (m MatchAll) CertificateMatches(_ *x509.Certificate) bool {
	return true
}

// PrecertificateMatches returns true if the given precert should match, in this case, always.
func (m MatchAll) PrecertificateMatches(_ *ct.Precertificate) bool {
	return true
}

// MatchNone is a Matcher which will never match any Certificate or Precertificate.
type MatchNone struct{}

// CertificateMatches returns true if the given cert should match; in this case, never.
func (m MatchNone) CertificateMatches(_ *x509.Certificate) bool {
	return false
}

// PrecertificateMatches returns true if the given cert should match; in this case, never.
func (m MatchNone) PrecertificateMatches(_ *ct.Precertificate) bool {
	return false
}

// MatchSerialNumber performs a match for a specific serial number.
type MatchSerialNumber struct {
	SerialNumber big.Int
}

// CertificateMatches returns true if the given cert should match; in this
// case, only if the serial number matches.
func (m MatchSerialNumber) CertificateMatches(c *x509.Certificate) bool {
	return c.SerialNumber.String() == m.SerialNumber.String()
}

// PrecertificateMatches returns true if the given cert should match; in this
// case, only if the serial number matches.
func (m MatchSerialNumber) PrecertificateMatches(p *ct.Precertificate) bool {
	return p.TBSCertificate.SerialNumber.String() == m.SerialNumber.String()
}

// MatchSubjectRegex is a Matcher which will use CertificateSubjectRegex and PrecertificateSubjectRegex
// to determine whether Certificates and Precertificates are interesting.
// The two regexes are tested against Subject CN (Common Name) as well as all
// Subject Alternative Names
type MatchSubjectRegex struct {
	CertificateSubjectRegex    *regexp.Regexp
	PrecertificateSubjectRegex *regexp.Regexp
}

// CertificateMatches returns true if either CN or any SAN of c matches m.CertificateSubjectRegex.
func (m MatchSubjectRegex) CertificateMatches(c *x509.Certificate) bool {
	if m.CertificateSubjectRegex.FindStringIndex(c.Subject.CommonName) != nil {
		return true
	}
	for _, alt := range c.DNSNames {
		if m.CertificateSubjectRegex.FindStringIndex(alt) != nil {
			return true
		}
	}
	return false
}

// PrecertificateMatches returns true if either CN or any SAN of p matches m.PrecertificateSubjectRegex.
func (m MatchSubjectRegex) PrecertificateMatches(p *ct.Precertificate) bool {
	if m.PrecertificateSubjectRegex.FindStringIndex(p.TBSCertificate.Subject.CommonName) != nil {
		return true
	}
	for _, alt := range p.TBSCertificate.DNSNames {
		if m.PrecertificateSubjectRegex.FindStringIndex(alt) != nil {
			return true
		}
	}
	return false
}

// MatchIssuerRegex matches on issuer CN (common name) by regex
type MatchIssuerRegex struct {
	CertificateIssuerRegex    *regexp.Regexp
	PrecertificateIssuerRegex *regexp.Regexp
}

// CertificateMatches returns true if the given cert's CN matches.
func (m MatchIssuerRegex) CertificateMatches(c *x509.Certificate) bool {
	return m.CertificateIssuerRegex.FindStringIndex(c.Issuer.CommonName) != nil
}

// PrecertificateMatches returns true if the given precert's CN matches.
func (m MatchIssuerRegex) PrecertificateMatches(p *ct.Precertificate) bool {
	return m.PrecertificateIssuerRegex.FindStringIndex(p.TBSCertificate.Issuer.CommonName) != nil
}

// MatchSCTTimestamp is a matcher which matches leaf entries with the specified Timestamp.
type MatchSCTTimestamp struct {
	Timestamp uint64
}

// Matches returns true if the timestamp embedded in the leaf matches the one
// specified by this matcher.
func (m MatchSCTTimestamp) Matches(leaf *ct.LeafEntry) bool {
	entry, _ := ct.LogEntryFromLeaf(1, leaf)
	if entry == nil {
		// Can't validate if we can't parse
		return false
	}
	return entry.Leaf.TimestampedEntry.Timestamp == m.Timestamp
}

// LeafMatcher describes how to match log entries, based on the Log LeafEntry
// (which includes the unparsed [pre-]certificate; clients should implement this
// interface to perform their own match criteria.
type LeafMatcher interface {
	Matches(*ct.LeafEntry) bool
}

// CertParseFailMatcher is a LeafMatcher which will match any Certificate or Precertificate that
// triggered an error on parsing.
type CertParseFailMatcher struct {
	MatchNonFatalErrs bool
}

// Matches returns true for parse errors.
func (m CertParseFailMatcher) Matches(leaf *ct.LeafEntry) bool {
	_, err := ct.LogEntryFromLeaf(1, leaf)
	if err != nil {
		if x509.IsFatal(err) {
			return true
		}
		return m.MatchNonFatalErrs
	}
	return false
}

// CertVerifyFailMatcher is a LeafMatcher which will match any Certificate or Precertificate that fails
// validation.  The PopulateRoots() method should be called before use.
type CertVerifyFailMatcher struct {
	roots *x509.CertPool
}

// PopulateRoots adds the accepted roots for the log to the pool for validation.
func (m *CertVerifyFailMatcher) PopulateRoots(ctx context.Context, logClient *client.LogClient) {
	if m.roots != nil {
		return
	}
	m.roots = x509.NewCertPool()
	roots, err := logClient.GetAcceptedRoots(ctx)
	if err != nil {
		log.Fatal(err)
	}
	for _, root := range roots {
		cert, _ := x509.ParseCertificate(root.Data)
		if cert != nil {
			m.roots.AddCert(cert)
		} else {
			log.Fatal(err)
		}
	}
}

// Matches returns true for validation errors.
func (m CertVerifyFailMatcher) Matches(leaf *ct.LeafEntry) bool {
	entry, _ := ct.LogEntryFromLeaf(1, leaf)
	if entry == nil {
		// Can't validate if we can't parse
		return false
	}
	// Validate the [pre-]certificate as of just before its expiry.
	var notBefore time.Time
	if entry.X509Cert != nil {
		notBefore = entry.X509Cert.NotAfter
	} else {
		notBefore = entry.Precert.TBSCertificate.NotAfter
	}
	when := notBefore.Add(-1 * time.Second)
	opts := x509.VerifyOptions{
		KeyUsages:     []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
		Roots:         m.roots,
		Intermediates: x509.NewCertPool(),
		CurrentTime:   when,
	}
	chain := make([]*x509.Certificate, len(entry.Chain))
	for ii, cert := range entry.Chain {
		intermediate, err := x509.ParseCertificate(cert.Data)
		if intermediate == nil {
			log.Printf("Intermediate %d fails to parse: %v", ii, err)
			return true
		}
		chain[ii] = intermediate
		opts.Intermediates.AddCert(intermediate)
	}
	if entry.X509Cert != nil {
		if _, err := entry.X509Cert.Verify(opts); err != nil {
			log.Printf("Cert fails to validate as of %v: %v", opts.CurrentTime, err)
			return true
		}
		return false
	}
	if entry.Precert != nil {
		precert, err := x509.ParseCertificate(entry.Precert.Submitted.Data)
		if err != nil {
			log.Printf("Precert fails to parse as of %v: %v", opts.CurrentTime, err)
			return true
		}
		// Ignore unhandled poison extension.
		dropUnhandledExtension(precert, x509.OIDExtensionCTPoison)

		for i := 1; i < len(chain); i++ {
			// PolicyConstraints is legal (and critical) but unparsed.
			dropUnhandledExtension(chain[i], x509.OIDExtensionPolicyConstraints)
		}

		// Drop CT EKU from preissuer if present.
		if len(chain) > 0 {
			for i, eku := range chain[0].ExtKeyUsage {
				if eku == x509.ExtKeyUsageCertificateTransparency {
					chain[0].ExtKeyUsage = append(chain[0].ExtKeyUsage[:i], chain[0].ExtKeyUsage[i+1:]...)
					break
				}
			}
		}

		if _, err := precert.Verify(opts); err != nil {
			log.Printf("Precert fails to validate as of %v: %v", opts.CurrentTime, err)
			return true
		}
		return false
	}
	log.Printf("Neither cert nor precert present!")
	return true
}

func dropUnhandledExtension(cert *x509.Certificate, oid asn1.ObjectIdentifier) {
	for j, extOID := range cert.UnhandledCriticalExtensions {
		if extOID.Equal(oid) {
			cert.UnhandledCriticalExtensions = append(cert.UnhandledCriticalExtensions[:j], cert.UnhandledCriticalExtensions[j+1:]...)
			return
		}
	}
}
