211 lines
4.5 KiB
Go
211 lines
4.5 KiB
Go
package certlib
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"git.wntrmute.dev/kyle/goutils/certlib/certerr"
|
|
)
|
|
|
|
// ReadCertificate reads a DER or PEM-encoded certificate from the
|
|
// byte slice.
|
|
func ReadCertificate(in []byte) (*x509.Certificate, []byte, error) {
|
|
in = bytes.TrimSpace(in)
|
|
if len(in) == 0 {
|
|
return nil, nil, certerr.ParsingError(certerr.ErrorSourceCertificate, certerr.ErrEmptyCertificate)
|
|
}
|
|
|
|
if in[0] == '-' {
|
|
p, remaining := pem.Decode(in)
|
|
if p == nil {
|
|
return nil, nil, certerr.ParsingError(certerr.ErrorSourceCertificate, errors.New("invalid PEM file"))
|
|
}
|
|
|
|
rest := remaining
|
|
if p.Type != pemTypeCertificate {
|
|
return nil, rest, certerr.ParsingError(
|
|
certerr.ErrorSourceCertificate,
|
|
certerr.ErrInvalidPEMType(p.Type, pemTypeCertificate),
|
|
)
|
|
}
|
|
|
|
in = p.Bytes
|
|
cert, err := x509.ParseCertificate(in)
|
|
if err != nil {
|
|
return nil, rest, certerr.ParsingError(certerr.ErrorSourceCertificate, err)
|
|
}
|
|
return cert, rest, nil
|
|
}
|
|
|
|
cert, err := x509.ParseCertificate(in)
|
|
if err != nil {
|
|
return nil, nil, certerr.ParsingError(certerr.ErrorSourceCertificate, err)
|
|
}
|
|
return cert, nil, nil
|
|
}
|
|
|
|
// ReadCertificates tries to read all the certificates in a
|
|
// PEM-encoded collection.
|
|
func ReadCertificates(in []byte) ([]*x509.Certificate, error) {
|
|
var cert *x509.Certificate
|
|
var certs []*x509.Certificate
|
|
var err error
|
|
for {
|
|
cert, in, err = ReadCertificate(in)
|
|
if err != nil {
|
|
break
|
|
}
|
|
|
|
if cert == nil {
|
|
break
|
|
}
|
|
|
|
certs = append(certs, cert)
|
|
if len(in) == 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
return certs, err
|
|
}
|
|
|
|
// LoadCertificate tries to read a single certificate from disk. If
|
|
// the file contains multiple certificates (e.g. a chain), only the
|
|
// first certificate is returned.
|
|
func LoadCertificate(path string) (*x509.Certificate, error) {
|
|
in, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, certerr.LoadingError(certerr.ErrorSourceCertificate, err)
|
|
}
|
|
|
|
cert, _, err := ReadCertificate(in)
|
|
return cert, err
|
|
}
|
|
|
|
// LoadCertificates tries to read all the certificates in a file,
|
|
// returning them in the order that it found them in the file.
|
|
func LoadCertificates(path string) ([]*x509.Certificate, error) {
|
|
in, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, certerr.LoadingError(certerr.ErrorSourceCertificate, err)
|
|
}
|
|
|
|
return ReadCertificates(in)
|
|
}
|
|
|
|
func PoolFromBytes(certBytes []byte) (*x509.CertPool, error) {
|
|
pool := x509.NewCertPool()
|
|
|
|
certs, err := ReadCertificates(certBytes)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read certificates: %w", err)
|
|
}
|
|
|
|
for _, cert := range certs {
|
|
pool.AddCert(cert)
|
|
}
|
|
|
|
return pool, nil
|
|
}
|
|
|
|
func ExportPrivateKeyPEM(priv crypto.PrivateKey) ([]byte, error) {
|
|
keyDER, err := x509.MarshalPKCS8PrivateKey(priv)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return pem.EncodeToMemory(&pem.Block{Type: pemTypePrivateKey, Bytes: keyDER}), nil
|
|
}
|
|
|
|
func LoadCSR(path string) (*x509.CertificateRequest, error) {
|
|
in, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, certerr.LoadingError(certerr.ErrorSourceCSR, err)
|
|
}
|
|
|
|
req, _, err := ParseCSR(in)
|
|
return req, err
|
|
}
|
|
|
|
func ExportCSRAsPEM(req *x509.CertificateRequest) []byte {
|
|
return pem.EncodeToMemory(&pem.Block{Type: pemTypeCertificateRequest, Bytes: req.Raw})
|
|
}
|
|
|
|
type FileFormat uint8
|
|
|
|
const (
|
|
FormatPEM FileFormat = iota + 1
|
|
FormatDER
|
|
)
|
|
|
|
func (f FileFormat) String() string {
|
|
switch f {
|
|
case FormatPEM:
|
|
return "PEM"
|
|
case FormatDER:
|
|
return "DER"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
type FileType struct {
|
|
Format FileFormat
|
|
Type string
|
|
}
|
|
|
|
func (ft FileType) String() string {
|
|
if ft.Type == "" {
|
|
return ft.Format.String()
|
|
}
|
|
return fmt.Sprintf("%s (%s)", ft.Type, ft.Format)
|
|
}
|
|
|
|
// FileKind returns the file type of the given file.
|
|
func FileKind(path string) (*FileType, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
block, _ := pem.Decode(data)
|
|
if block != nil {
|
|
return &FileType{
|
|
Format: FormatPEM,
|
|
Type: strings.ToLower(strings.TrimSpace(block.Type)),
|
|
}, nil
|
|
}
|
|
|
|
_, err = x509.ParseCertificate(data)
|
|
if err == nil {
|
|
return &FileType{
|
|
Format: FormatDER,
|
|
Type: strings.ToLower(pemTypeCertificate),
|
|
}, nil
|
|
}
|
|
|
|
_, err = x509.ParseCertificateRequest(data)
|
|
if err == nil {
|
|
return &FileType{
|
|
Format: FormatDER,
|
|
Type: strings.ToLower(pemTypeCertificateRequest),
|
|
}, nil
|
|
}
|
|
|
|
_, err = x509.ParsePKCS8PrivateKey(data)
|
|
if err == nil {
|
|
return &FileType{
|
|
Format: FormatDER,
|
|
Type: strings.ToLower(pemTypePrivateKey),
|
|
}, nil
|
|
}
|
|
|
|
return nil, errors.New("certlib; unknown file type")
|
|
}
|