tls

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 20, 2023 License: GPL-3.0 Imports: 23 Imported by: 0

Documentation

Index

Constants

View Source
const (
	KeyOutDir              = "out.dir"
	KeyOutCert             = "out.cert"
	KeyOutKey              = "out.key"
	KeyOutCA               = "out.ca"
	KeyCommonName          = "commonName"
	KeyIsCA                = "isCA"
	KeyDuration            = "duration"
	KeyRenewBefore         = "renewBefore"
	KeyKeyUsages           = "keyUsages"
	KeyExtKeyUsages        = "extKeyUsages"
	KeyDNSNames            = "dnsNames"
	KeyIPAddresses         = "ipAddresses"
	KeyCountries           = "subject.countries"
	KeyOrganizations       = "subject.organizations"
	KeyOrganizationalUnits = "subject.organizationalUnits"
	KeyLocalities          = "subject.localities"
	KeyProvinces           = "subject.provinces"
	KeyStreetAddresses     = "subject.streetAddresses"
	KeyPostalCodes         = "subject.postalCodes"
	KeyPrivateKeyAlgorithm = "privateKey.algorithm"
	KeyPrivateKeySize      = "privateKey.size"
	KeyIssuerDir           = "issuer.dir"
	KeyIssuerPublicKey     = "issuer.publicKey"
	KeyIssuerPrivateKey    = "issuer.privateKey"
)
View Source
const (
	MinRSAKeySize = 2048
	MaxRSAKeySize = 8192
	RSA           = "rsa"
	ECDSA         = "ecdsa"
	ED25519       = "ed25519"
)

Variables

View Source
var (
	ErrOpenCertificateRequestFile = errors.New("open file")
	ErrReadCertificateRequestFile = errors.New("read file")
	ErrInvalidKeyUsages           = errors.New("invalid key usages")
	ErrInvalidExtKeyUsages        = errors.New("invalid ext key usages")
	ErrInvalidIPAddress           = errors.New("invalid ip addresses")
	ErrMissingMandatoryField      = errors.New("missing mandatory field")
)
View Source
var (
	ErrLoadIssuerKeyPair      = errors.New("load issuer key pair")
	ErrParseIssuerCertificate = errors.New("parse issuer certificate")
	ErrCreateFile             = errors.New("create file")
	ErrReadFile               = errors.New("read file")
	ErrParseCertificate       = errors.New("parse certificate")
	ErrEncode                 = errors.New("encode")
	ErrReadDir                = errors.New("read directory")
)
View Source
var (
	ErrGenerateKey                    = errors.New("generate key")
	ErrGenerateSerialNumber           = errors.New("generate serial number")
	ErrGenerateCert                   = errors.New("generate cert")
	ErrCopyCA                         = errors.New("copy CA")
	ErrRSAKeySizeTooWeak              = fmt.Errorf("RSA key size too weak, minimum is %d", MinRSAKeySize)
	ErrRSAKeySizeTooBig               = fmt.Errorf("RSA key size too big, maximum is %d", MaxRSAKeySize)
	ErrUnsupportedPrivateKeyAlgorithm = fmt.Errorf("unsupported private key algorithm")
	ErrEncodePrivateKey               = fmt.Errorf("encode private key")
	ErrUnsupportedECDSAKeySize        = errors.New("unsupported ecdsa key size")
)
View Source
var CopyCA = func(issuer *Issuer, path string) error {
	pemCert := &pem.Block{Type: "CERTIFICATE", Bytes: issuer.PublicKey.Raw}
	err := WritePemToFile(pemCert, path)
	if err != nil {
		return fmt.Errorf(format.WrapErrors, ErrCopyCA, err)
	}
	return nil
}
View Source
var (
	ErrInvalidPEMBlock = errors.New("invalid PEM block")
)
View Source
var FileDoesNotExists = func(file string) bool {
	_, err := os.Stat(file)
	return errors.Is(err, os.ErrNotExist)
}
View Source
var GenerateCertificate = func(req CertificateRequest, key crypto.PrivateKey, issuer *Issuer) error {
	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	if err != nil {
		return fmt.Errorf(format.WrapErrors, ErrGenerateSerialNumber, err)
	}

	keyUsage := x509.KeyUsageDigitalSignature

	if _, isRSA := key.(*rsa.PrivateKey); isRSA {
		keyUsage |= x509.KeyUsageKeyEncipherment
	}

	if req.IsCA {
		keyUsage |= x509.KeyUsageCertSign
	}

	notBefore := time.Now()
	template := &x509.Certificate{
		Subject: pkix.Name{
			CommonName:         req.CommonName,
			Country:            req.Countries,
			Organization:       req.Organizations,
			OrganizationalUnit: req.OrganizationalUnits,
			Locality:           req.Localities,
			Province:           req.Provinces,
			StreetAddress:      req.StreetAddresses,
			PostalCode:         req.PostalCodes,
		},
		SerialNumber:          serialNumber,
		IsCA:                  req.IsCA,
		NotBefore:             notBefore,
		NotAfter:              notBefore.Add(req.Duration),
		KeyUsage:              keyUsage,
		ExtKeyUsage:           req.ExtKeyUsage,
		DNSNames:              req.DNSNames,
		IPAddresses:           req.IPAddresses,
		BasicConstraintsValid: true,
	}

	issuerCert := template
	signerKey := key
	if issuer != nil {
		issuerCert = issuer.PublicKey
		signerKey = issuer.PrivateKey
	}

	certBytes, err := x509.CreateCertificate(rand.Reader, template, issuerCert, publicKey(key), signerKey)
	if err != nil {
		return fmt.Errorf(format.WrapErrors, ErrGenerateCert, err)
	}

	pemCert := &pem.Block{Type: "CERTIFICATE", Bytes: certBytes}
	err = WritePemToFile(pemCert, req.OutCertPath)
	if err != nil {
		return fmt.Errorf(format.WrapErrors, ErrGenerateCert, err)
	}

	return nil
}
View Source
var GenerateOutFilesFromRequest = func(req CertificateRequest, issuer *Issuer) {
	logrus.Infof("Generate key to %s", req.OutKeyPath)
	key, err := GeneratePrivateKey(req)
	if err != nil {
		logError(err)
		return
	}

	logrus.Infof("Generate certificate to %s", req.OutCertPath)
	if err := GenerateCertificate(req, key, issuer); err != nil {
		logError(err)
		return
	}

	if issuer != nil {
		logrus.Infof("Copy CA to %s", req.OutCAPath)
		if err := CopyCA(issuer, req.OutCAPath); err != nil {
			logError(err)
			return
		}
	}
}
View Source
var GeneratePrivateKey = func(req CertificateRequest) (crypto.PrivateKey, error) {
	algorithm := req.PrivateKey.Algorithm
	if algorithm == "" {
		algorithm = RSA
	}

	var key crypto.PrivateKey
	var pemBlock *pem.Block
	var err error

	switch strings.ToLower(algorithm) {
	case RSA:
		key, pemBlock, err = generateRSAPrivateKey(req)
	case ECDSA:
		key, pemBlock, err = generateECPrivateKey(req)
	case ED25519:
		key, pemBlock, err = generateEd25519PrivateKey(req)
	default:
		return nil, fmt.Errorf(format.WrapErrorString, ErrUnsupportedPrivateKeyAlgorithm, algorithm)
	}

	if err != nil {
		return nil, fmt.Errorf(format.WrapErrors, ErrGenerateKey, err)
	}

	err = WritePemToFile(pemBlock, req.OutKeyPath)
	if err != nil {
		return nil, fmt.Errorf(format.WrapErrors, ErrGenerateKey, err)
	}

	return key, nil
}
View Source
var HandleCertificateRequestFile = func(file string) {

	if _, err := config.GetExtension(file); err != nil {
		return
	}

	logrus.Infof("Handle certificate request %s", file)
	req, err := LoadCertificateRequest(file)
	if err != nil {
		logrus.Errorf("Failed to load certificate request: %v", err)
		return
	}

	issuer, err := LoadIssuer(req.IssuerPath)
	if err != nil {
		logrus.Errorf("Invalid issuer: %v", err)
		return
	}

	if FileDoesNotExists(req.OutCertPath) {
		if ok := MakeParentsDirectories(req.OutCertPath); !ok {
			return
		}
		GenerateOutFilesFromRequest(req, issuer)
		return
	}

	cert, err := LoadCertFromFile(req.OutCertPath)
	if err != nil {
		logrus.Errorf("Invalid certificate %s: %v", req.OutCertPath, err)
		GenerateOutFilesFromRequest(req, issuer)
		return
	}

	if cert.NotAfter.Before(time.Now().Add(req.RenewBefore)) {
		logrus.Infof("Expired certificate %s", req.OutCertPath)
		GenerateOutFilesFromRequest(req, issuer)
		return
	}
}
View Source
var LoadCertFromFile = func(file string) (*x509.Certificate, error) {
	b, err := os.ReadFile(file)
	if err != nil {
		return nil, fmt.Errorf(format.WrapErrors, ErrReadFile, err)
	}

	certPEMBlock, _ := pem.Decode(b)
	if certPEMBlock == nil || certPEMBlock.Type != "CERTIFICATE" {
		return nil, ErrInvalidPEMBlock
	}

	x509Cert, err := x509.ParseCertificate(certPEMBlock.Bytes)
	if err != nil {
		return nil, fmt.Errorf(format.WrapErrors, ErrParseCertificate, err)
	}

	return x509Cert, nil
}
View Source
var LoadCertificateRequest = func(path string) (CertificateRequest, error) {
	conf := viper.New()
	file, err := os.Open(path)
	if err != nil {
		return CertificateRequest{}, fmt.Errorf(format.WrapErrors, ErrOpenCertificateRequestFile, err)
	}
	ext, err := config.GetExtension(path)
	if err != nil {
		return CertificateRequest{}, err
	}
	conf.SetConfigType(ext)
	if err := conf.ReadConfig(file); err != nil {
		return CertificateRequest{}, fmt.Errorf(format.WrapErrors, ErrReadCertificateRequestFile, err)
	}

	conf.SetDefault(KeyOutCert, "tls.crt")
	conf.SetDefault(KeyOutKey, "tls.key")
	conf.SetDefault(KeyOutCA, "ca.crt")
	conf.SetDefault(KeyCountries, config.DefaultCountries)
	conf.SetDefault(KeyOrganizations, config.DefaultOrganizations)
	conf.SetDefault(KeyOrganizationalUnits, config.DefaultOrganizationalUnits)
	conf.SetDefault(KeyLocalities, config.DefaultLocalities)
	conf.SetDefault(KeyProvinces, config.DefaultProvinces)
	conf.SetDefault(KeyStreetAddresses, config.DefaultStreetAddresses)
	conf.SetDefault(KeyPostalCodes, config.DefaultPostalCodes)
	conf.SetDefault(KeyIssuerPublicKey, "ca.crt")
	conf.SetDefault(KeyIssuerPrivateKey, "ca.key")

	outDir := conf.GetString(KeyOutDir)
	if outDir == "" {
		return CertificateRequest{}, fmt.Errorf(format.WrapErrorString, ErrMissingMandatoryField, KeyOutDir)
	}

	issuerDir := conf.GetString(KeyIssuerDir)
	var issuerPath IssuerPath
	if issuerDir != "" {
		issuerPubKeyPath := filepath.Join(issuerDir, conf.GetString(KeyIssuerPublicKey))
		issuerPrivKeyPath := filepath.Join(issuerDir, conf.GetString(KeyIssuerPrivateKey))
		issuerPath = IssuerPath{PublicKey: issuerPubKeyPath, PrivateKey: issuerPrivKeyPath}
	}

	req := CertificateRequest{
		OutCertPath:         filepath.Join(outDir, conf.GetString(KeyOutCert)),
		OutKeyPath:          filepath.Join(outDir, conf.GetString(KeyOutKey)),
		OutCAPath:           filepath.Join(outDir, conf.GetString(KeyOutCA)),
		CommonName:          conf.GetString(KeyCommonName),
		IsCA:                conf.GetBool(KeyIsCA),
		Countries:           conf.GetStringSlice(KeyCountries),
		Organizations:       conf.GetStringSlice(KeyOrganizations),
		OrganizationalUnits: conf.GetStringSlice(KeyOrganizationalUnits),
		Localities:          conf.GetStringSlice(KeyLocalities),
		Provinces:           conf.GetStringSlice(KeyProvinces),
		StreetAddresses:     conf.GetStringSlice(KeyStreetAddresses),
		PostalCodes:         conf.GetStringSlice(KeyPostalCodes),
		Duration:            conf.GetDuration(KeyDuration),
		RenewBefore:         conf.GetDuration(KeyRenewBefore),
		PrivateKey:          PrivateKey{Algorithm: conf.GetString(KeyPrivateKeyAlgorithm), Size: conf.GetInt(KeyPrivateKeySize)},
		IssuerPath:          issuerPath,
	}

	for _, s := range conf.GetStringSlice(KeyKeyUsages) {
		keyUsage, err := findKeyUsage(s)
		if err != nil {
			return CertificateRequest{}, fmt.Errorf(format.WrapErrorString, ErrInvalidKeyUsages, s)
		}
		req.KeyUsage |= keyUsage
	}

	for _, s := range conf.GetStringSlice(KeyExtKeyUsages) {
		extKeyUsage, err := findExtKeyUsage(s)
		if err != nil {
			return CertificateRequest{}, fmt.Errorf(format.WrapErrorString, ErrInvalidExtKeyUsages, s)
		}
		req.ExtKeyUsage = append(req.ExtKeyUsage, extKeyUsage)
	}

	for _, dnsName := range conf.GetStringSlice(KeyDNSNames) {
		req.DNSNames = append(req.DNSNames, dnsName)
	}

	for _, s := range conf.GetStringSlice(KeyIPAddresses) {
		ipAddr := net.ParseIP(s)
		if ipAddr == nil {
			return CertificateRequest{}, fmt.Errorf(format.WrapErrorString, ErrInvalidIPAddress, s)
		}
		req.IPAddresses = append(req.IPAddresses, ipAddr)
	}

	return req, nil
}
View Source
var LoadCertificateRequests = func(dir string) {
	files, err := ReadDir(dir)
	if err != nil {
		logrus.Errorf("Failed to read directory %s: %v", dir, err)
		return
	}
	for _, file := range files {
		HandleCertificateRequestFile(file)
	}
}
View Source
var LoadIssuer = func(path IssuerPath) (*Issuer, error) {
	if path.PublicKey == "" || path.PrivateKey == "" {
		return nil, nil
	}
	rootCA, err := tls.LoadX509KeyPair(path.PublicKey, path.PrivateKey)
	if err != nil {
		return nil, fmt.Errorf(format.WrapErrors, ErrLoadIssuerKeyPair, err)
	}
	caKey := rootCA.PrivateKey
	ca, err := x509.ParseCertificate(rootCA.Certificate[0])
	if err != nil {
		return nil, fmt.Errorf(format.WrapErrors, ErrParseIssuerCertificate, err)
	}
	return &Issuer{PublicKey: ca, PrivateKey: caKey}, nil
}
View Source
var MakeParentsDirectories = func(path string) bool {
	dir := filepath.Dir(path)
	if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
		if err := os.MkdirAll(dir, 0755); err != nil {
			return false
		}
	}
	return true
}
View Source
var ReadDir = func(dir string) ([]string, error) {
	entries, err := os.ReadDir(dir)
	if err != nil {
		return nil, fmt.Errorf(format.WrapErrors, ErrReadDir, err)
	}
	files := make([]string, 0, len(entries))
	for _, entry := range entries {
		info, _ := entry.Info()
		if !info.IsDir() {
			files = append(files, filepath.Join(dir, info.Name()))
		}
	}
	return files, nil
}
View Source
var WritePemToFile = func(b *pem.Block, file string) error {
	pemFile, err := os.Create(file)
	if err != nil {
		return fmt.Errorf(format.WrapErrors, ErrCreateFile, err)
	}
	defer func() { _ = pemFile.Close() }()
	err = pem.Encode(pemFile, b)
	if err != nil {
		return fmt.Errorf(format.WrapErrors, ErrEncode, err)
	}
	return nil
}

Functions

func Start

func Start() funcs.Stop

Types

type CertificateRequest

type CertificateRequest struct {
	OutCertPath         string
	OutKeyPath          string
	OutCAPath           string
	CommonName          string
	IsCA                bool
	Countries           []string
	Organizations       []string
	OrganizationalUnits []string
	Localities          []string
	Provinces           []string
	StreetAddresses     []string
	PostalCodes         []string
	Duration            time.Duration
	RenewBefore         time.Duration
	KeyUsage            x509.KeyUsage
	ExtKeyUsage         []x509.ExtKeyUsage
	DNSNames            []string
	IPAddresses         []net.IP
	PrivateKey          PrivateKey
	IssuerPath          IssuerPath
}

type Issuer

type Issuer struct {
	PublicKey  *x509.Certificate
	PrivateKey crypto.PrivateKey
}

type IssuerPath

type IssuerPath struct {
	PublicKey  string
	PrivateKey string
}

type PrivateKey

type PrivateKey struct {
	Algorithm string
	Size      int
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL