// Copyright 2023 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 fallback embeds a set of fallback X.509 trusted roots in the
// application by automatically invoking [x509.SetFallbackRoots]. This allows
// the application to work correctly even if the operating system does not
// provide a verifier or system roots pool.
//
// To use it, import the package like
//
//	import _ "golang.org/x/crypto/x509roots/fallback"
//
// It's recommended that only binaries, and not libraries, import this package.
//
// This package must be kept up to date for security and compatibility reasons.
// Use govulncheck to be notified of when new versions of the package are
// available.
package fallback

import (
	"crypto/x509"
	_ "embed"
	"fmt"
	"time"
)

//go:embed bundle.der
var rawCerts []byte

func init() {
	x509.SetFallbackRoots(newFallbackCertPool())
}

func newFallbackCertPool() *x509.CertPool {
	p := x509.NewCertPool()
	for _, c := range mustParse(unparsedCertificates) {
		if len(c.constraints) == 0 {
			p.AddCert(c.cert)
		} else {
			p.AddCertWithConstraint(c.cert, func(chain []*x509.Certificate) error {
				for _, constraint := range c.constraints {
					if err := constraint(chain); err != nil {
						return err
					}
				}
				return nil
			})
		}
	}
	return p
}

type unparsedCertificate struct {
	cn           string
	sha256Hash   string
	certStartOff int
	certLength   int

	// possible constraints
	distrustAfter string
}

type parsedCertificate struct {
	cert        *x509.Certificate
	constraints []func([]*x509.Certificate) error
}

func mustParse(unparsedCerts []unparsedCertificate) []parsedCertificate {
	b := make([]parsedCertificate, 0, len(unparsedCerts))
	for _, unparsed := range unparsedCerts {
		cert, err := x509.ParseCertificate(rawCerts[unparsed.certStartOff : unparsed.certStartOff+unparsed.certLength])
		if err != nil {
			panic(err)
		}
		parsed := parsedCertificate{cert: cert}
		// parse possible constraints, this should check all fields of unparsedCertificate.
		if unparsed.distrustAfter != "" {
			distrustAfter, err := time.Parse(time.RFC3339, unparsed.distrustAfter)
			if err != nil {
				panic(fmt.Sprintf("failed to parse distrustAfter %q: %s", unparsed.distrustAfter, err))
			}
			parsed.constraints = append(parsed.constraints, func(chain []*x509.Certificate) error {
				for _, c := range chain {
					if c.NotBefore.After(distrustAfter) {
						return fmt.Errorf("certificate issued after distrust-after date %q", distrustAfter)
					}
				}
				return nil
			})
		}
		b = append(b, parsed)
	}
	return b
}
