//go:build ignore
// +build ignore

/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

//go:generate -command gencerts go run ./generate.go
//go:generate gencerts -orgs 2 -child-orgs 2 -servers 2 -clients 2

package main

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"flag"
	"fmt"
	"math/big"
	"net"
	"os"
	"time"
)

// command line flags
var (
	numOrgs        = flag.Int("orgs", 2, "number of unique organizations")
	numChildOrgs   = flag.Int("child-orgs", 2, "number of intermediaries per organization")
	numClientCerts = flag.Int("clients", 1, "number of client certificates per organization")
	numServerCerts = flag.Int("servers", 1, "number of server certificates per organization")
)

// default template for X509 subject
func subjectTemplate() pkix.Name {
	return pkix.Name{
		Country:  []string{"US"},
		Locality: []string{"San Francisco"},
		Province: []string{"California"},
	}
}

// default template for X509 certificates
func x509Template() (x509.Certificate, error) {
	// generate a serial number
	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	if err != nil {
		return x509.Certificate{}, err
	}

	now := time.Now()
	// basic template to use
	x509 := x509.Certificate{
		SerialNumber:          serialNumber,
		NotBefore:             now,
		NotAfter:              now.Add(3650 * 24 * time.Hour), //~ten years
		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		BasicConstraintsValid: true,
	}
	return x509, nil
}

// generate an EC private key (P256 curve)
func genKeyECDSA(name string) (*ecdsa.PrivateKey, error) {
	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return nil, err
	}
	// write key out to file
	keyBytes, err := x509.MarshalECPrivateKey(priv)
	if err != nil {
		return nil, err
	}
	keyFile, err := os.OpenFile(name+"-key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
	if err != nil {
		return nil, err
	}
	defer keyFile.Close()
	err = pem.Encode(keyFile, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes})
	if err != nil {
		return nil, err
	}
	return priv, nil
}

// generate a signed X509 certficate using ECDSA
func genCertificateECDSA(
	name string,
	template, parent *x509.Certificate,
	pub *ecdsa.PublicKey, priv *ecdsa.PrivateKey,
) (*x509.Certificate, error) {
	// create the x509 public cert
	certBytes, err := x509.CreateCertificate(rand.Reader, template, parent, pub, priv)
	if err != nil {
		return nil, err
	}
	x509Cert, err := x509.ParseCertificate(certBytes)
	if err != nil {
		return nil, err
	}

	// write cert out to file
	certFile, err := os.Create(name + "-cert.pem")
	if err != nil {
		return nil, err
	}
	defer certFile.Close()

	// pem encode the cert
	err = pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})
	if err != nil {
		return nil, err
	}

	return x509Cert, nil
}

// generate an EC certificate appropriate for use by a TLS server
func genServerCertificateECDSA(name string, signKey *ecdsa.PrivateKey, signCert *x509.Certificate) error {
	fmt.Println(name)

	key, err := genKeyECDSA(name)
	if err != nil {
		return err
	}
	template, err := x509Template()
	if err != nil {
		return err
	}

	template.ExtKeyUsage = []x509.ExtKeyUsage{
		x509.ExtKeyUsageServerAuth,
		x509.ExtKeyUsageClientAuth,
	}

	// set the organization for the subject
	subject := subjectTemplate()
	subject.Organization = []string{name}
	subject.CommonName = "localhost"

	template.Subject = subject
	template.DNSNames = []string{"localhost"}
	template.IPAddresses = []net.IP{
		net.ParseIP("127.0.0.1"),
		net.ParseIP("::1"),
	}

	_, err = genCertificateECDSA(name, &template, signCert, &key.PublicKey, signKey)
	if err != nil {
		return err
	}

	return nil
}

// generate an EC certificate appropriate for use by a TLS server
func genClientCertificateECDSA(name string, signKey *ecdsa.PrivateKey, signCert *x509.Certificate) error {
	fmt.Println(name)

	key, err := genKeyECDSA(name)
	if err != nil {
		return err
	}
	template, err := x509Template()
	if err != nil {
		return err
	}
	template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}

	// set the organization for the subject
	subject := subjectTemplate()
	subject.Organization = []string{name}
	subject.CommonName = name
	template.Subject = subject

	_, err = genCertificateECDSA(name, &template, signCert, &key.PublicKey, signKey)
	if err != nil {
		return err
	}

	return nil
}

// generate an EC certificate signing(CA) key pair and output as
// PEM-encoded files
func genCertificateAuthorityECDSA(name string) (*ecdsa.PrivateKey, *x509.Certificate, error) {
	key, err := genKeyECDSA(name)
	if err != nil {
		return nil, nil, err
	}
	template, err := x509Template()
	if err != nil {
		return nil, nil, err
	}

	// this is a CA
	template.IsCA = true
	template.KeyUsage |= x509.KeyUsageCertSign | x509.KeyUsageCRLSign
	template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageAny}

	// set the organization for the subject
	subject := subjectTemplate()
	subject.Organization = []string{name}
	subject.CommonName = name

	template.Subject = subject
	template.SubjectKeyId = []byte{1, 2, 3, 4}

	x509Cert, err := genCertificateECDSA(name, &template, &template, &key.PublicKey, key)
	if err != nil {
		return nil, nil, err
	}
	return key, x509Cert, nil
}

// generate an EC certificate appropriate for use by a TLS server
func genIntermediateCertificateAuthorityECDSA(
	name string,
	signKey *ecdsa.PrivateKey,
	signCert *x509.Certificate,
) (*ecdsa.PrivateKey, *x509.Certificate, error) {
	fmt.Println(name)

	key, err := genKeyECDSA(name)
	if err != nil {
		return nil, nil, err
	}
	template, err := x509Template()
	if err != nil {
		return nil, nil, err
	}

	// this is a CA
	template.IsCA = true
	template.KeyUsage |= x509.KeyUsageCertSign | x509.KeyUsageCRLSign
	template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageAny}

	// set the organization for the subject
	subject := subjectTemplate()
	subject.Organization = []string{name}
	subject.CommonName = name

	template.Subject = subject
	template.SubjectKeyId = []byte{1, 2, 3, 4}

	x509Cert, err := genCertificateECDSA(name, &template, signCert, &key.PublicKey, signKey)
	if err != nil {
		return nil, nil, err
	}

	return key, x509Cert, nil
}

const baseOrgName = "Org"

func main() {
	flag.Parse()
	fmt.Printf("Generating %d organizations each with %d server(s) and %d client(s)\n", *numOrgs, *numServerCerts, *numClientCerts)

	// generate orgs / CAs
	for i := 1; i <= *numOrgs; i++ {
		signKey, signCert, err := genCertificateAuthorityECDSA(fmt.Sprintf(baseOrgName+"%d", i))
		if err != nil {
			fmt.Printf("error generating CA %s%d : %s\n", baseOrgName, i, err.Error())
		}
		// generate server certificates for the org
		for j := 1; j <= *numServerCerts; j++ {
			name := fmt.Sprintf(baseOrgName+"%d-server%d", i, j)
			if err := genServerCertificateECDSA(name, signKey, signCert); err != nil {
				fmt.Printf("error generating server certificate for %s: %s\n", name, err.Error())
			}
		}
		// generate client certificates for the org
		for k := 1; k <= *numClientCerts; k++ {
			name := fmt.Sprintf(baseOrgName+"%d-client%d", i, k)
			if err := genClientCertificateECDSA(name, signKey, signCert); err != nil {
				fmt.Printf("error generating client certificate for %s: %s\n", name, err.Error())
			}
		}
		// generate child orgs (intermediary authorities)
		for m := 1; m <= *numChildOrgs; m++ {
			name := fmt.Sprintf(baseOrgName+"%d-child%d", i, m)
			childSignKey, childSignCert, err := genIntermediateCertificateAuthorityECDSA(name, signKey, signCert)
			if err != nil {
				fmt.Printf("error generating CA %s: %s\n", name, err.Error())
			}
			// generate server certificates for the child org
			for n := 1; n <= *numServerCerts; n++ {
				name := fmt.Sprintf(baseOrgName+"%d-child%d-server%d", i, m, n)
				if err := genServerCertificateECDSA(name, childSignKey, childSignCert); err != nil {
					fmt.Printf("error generating server certificate for %s: %s\n", name, err.Error())
				}
			}
			// generate client certificates for the child org
			for p := 1; p <= *numClientCerts; p++ {
				name := fmt.Sprintf(baseOrgName+"%d-child%d-client%d", i, m, p)
				if err := genClientCertificateECDSA(name, childSignKey, childSignCert); err != nil {
					fmt.Printf("error generating server certificate for %s: %s\n", name, err.Error())
				}
			}
		}
	}
}
