Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b1a2039c7d | |||
| 46c9976e73 | |||
| 5a5dd5e6ea | |||
| 3317b8c33b | |||
| fb1b1ffcad | |||
| 7bb6973341 | |||
| 8e997bda34 | |||
| d76db4a947 | |||
| 7e36a828d4 | |||
| 8eaca580be |
15
CHANGELOG
15
CHANGELOG
@@ -1,5 +1,20 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
|
|
||||||
|
v1.15.6 - 2025-11-19
|
||||||
|
certlib: add FileKind function to determine file type.
|
||||||
|
|
||||||
|
v1.15.5 - 2025-11-19
|
||||||
|
certlib/bundler: add support for crt files that are pem-encoded.
|
||||||
|
|
||||||
|
v1.15.4 - 2025-11-19
|
||||||
|
Quality of life fixes for CSR generation.
|
||||||
|
|
||||||
|
v1.15.3 - 2025-11-19
|
||||||
|
Minor bug fixes.
|
||||||
|
|
||||||
|
v1.15.2 - 2025-11-19
|
||||||
|
Minor bug fixes.
|
||||||
|
|
||||||
v1.15.1 - 2025-11-19
|
v1.15.1 - 2025-11-19
|
||||||
|
|
||||||
Changed:
|
Changed:
|
||||||
|
|||||||
@@ -422,6 +422,24 @@ func encodeCertsToFiles(
|
|||||||
name: baseName + ".pem",
|
name: baseName + ".pem",
|
||||||
content: pemContent,
|
content: pemContent,
|
||||||
})
|
})
|
||||||
|
case "crt":
|
||||||
|
pemContent := encodeCertsToPEM(certs)
|
||||||
|
files = append(files, fileEntry{
|
||||||
|
name: baseName + ".crt",
|
||||||
|
content: pemContent,
|
||||||
|
})
|
||||||
|
case "pemcrt":
|
||||||
|
pemContent := encodeCertsToPEM(certs)
|
||||||
|
files = append(files, fileEntry{
|
||||||
|
name: baseName + ".pem",
|
||||||
|
content: pemContent,
|
||||||
|
})
|
||||||
|
|
||||||
|
pemContent = encodeCertsToPEM(certs)
|
||||||
|
files = append(files, fileEntry{
|
||||||
|
name: baseName + ".crt",
|
||||||
|
content: pemContent,
|
||||||
|
})
|
||||||
case "der":
|
case "der":
|
||||||
if isSingle {
|
if isSingle {
|
||||||
// For single file in DER, concatenate all cert DER bytes
|
// For single file in DER, concatenate all cert DER bytes
|
||||||
|
|||||||
@@ -63,12 +63,7 @@ type CertificateRequest struct {
|
|||||||
Profile Profile `yaml:"profile"`
|
Profile Profile `yaml:"profile"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs CertificateRequest) Generate() (crypto.PrivateKey, *x509.CertificateRequest, error) {
|
func (cs CertificateRequest) Request(priv crypto.PrivateKey) (*x509.CertificateRequest, error) {
|
||||||
pub, priv, err := cs.KeySpec.Generate()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
subject := pkix.Name{}
|
subject := pkix.Name{}
|
||||||
subject.CommonName = cs.Subject.CommonName
|
subject.CommonName = cs.Subject.CommonName
|
||||||
subject.Country = []string{cs.Subject.Country}
|
subject.Country = []string{cs.Subject.Country}
|
||||||
@@ -81,13 +76,13 @@ func (cs CertificateRequest) Generate() (crypto.PrivateKey, *x509.CertificateReq
|
|||||||
for i, ip := range cs.Subject.IPAddresses {
|
for i, ip := range cs.Subject.IPAddresses {
|
||||||
ipAddresses = append(ipAddresses, net.ParseIP(ip))
|
ipAddresses = append(ipAddresses, net.ParseIP(ip))
|
||||||
if ipAddresses[i] == nil {
|
if ipAddresses[i] == nil {
|
||||||
return nil, nil, fmt.Errorf("invalid IP address: %s", ip)
|
return nil, fmt.Errorf("invalid IP address: %s", ip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
req := &x509.CertificateRequest{
|
req := &x509.CertificateRequest{
|
||||||
PublicKeyAlgorithm: 0,
|
PublicKeyAlgorithm: 0,
|
||||||
PublicKey: pub,
|
PublicKey: getPublic(priv),
|
||||||
Subject: subject,
|
Subject: subject,
|
||||||
DNSNames: cs.Subject.DNSNames,
|
DNSNames: cs.Subject.DNSNames,
|
||||||
IPAddresses: ipAddresses,
|
IPAddresses: ipAddresses,
|
||||||
@@ -95,12 +90,26 @@ func (cs CertificateRequest) Generate() (crypto.PrivateKey, *x509.CertificateReq
|
|||||||
|
|
||||||
reqBytes, err := x509.CreateCertificateRequest(rand.Reader, req, priv)
|
reqBytes, err := x509.CreateCertificateRequest(rand.Reader, req, priv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to create certificate request: %w", err)
|
return nil, fmt.Errorf("failed to create certificate request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err = x509.ParseCertificateRequest(reqBytes)
|
req, err = x509.ParseCertificateRequest(reqBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to parse certificate request: %w", err)
|
return nil, fmt.Errorf("failed to parse certificate request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs CertificateRequest) Generate() (crypto.PrivateKey, *x509.CertificateRequest, error) {
|
||||||
|
_, priv, err := cs.KeySpec.Generate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := cs.Request(priv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return priv, req, nil
|
return priv, req, nil
|
||||||
|
|||||||
@@ -71,3 +71,16 @@ func GenerateKey(algorithm x509.PublicKeyAlgorithm, bitSize int) (crypto.PublicK
|
|||||||
|
|
||||||
return pub, key, nil
|
return pub, key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getPublic(priv crypto.PrivateKey) crypto.PublicKey {
|
||||||
|
switch priv := priv.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
return &priv.PublicKey
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
return &priv.PublicKey
|
||||||
|
case *ed25519.PrivateKey:
|
||||||
|
return priv.Public()
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package certlib
|
package certlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.wntrmute.dev/kyle/goutils/certlib/certerr"
|
"git.wntrmute.dev/kyle/goutils/certlib/certerr"
|
||||||
)
|
)
|
||||||
@@ -13,6 +16,7 @@ import (
|
|||||||
// ReadCertificate reads a DER or PEM-encoded certificate from the
|
// ReadCertificate reads a DER or PEM-encoded certificate from the
|
||||||
// byte slice.
|
// byte slice.
|
||||||
func ReadCertificate(in []byte) (*x509.Certificate, []byte, error) {
|
func ReadCertificate(in []byte) (*x509.Certificate, []byte, error) {
|
||||||
|
in = bytes.TrimSpace(in)
|
||||||
if len(in) == 0 {
|
if len(in) == 0 {
|
||||||
return nil, nil, certerr.ParsingError(certerr.ErrorSourceCertificate, certerr.ErrEmptyCertificate)
|
return nil, nil, certerr.ParsingError(certerr.ErrorSourceCertificate, certerr.ErrEmptyCertificate)
|
||||||
}
|
}
|
||||||
@@ -24,10 +28,10 @@ func ReadCertificate(in []byte) (*x509.Certificate, []byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rest := remaining
|
rest := remaining
|
||||||
if p.Type != "CERTIFICATE" {
|
if p.Type != pemTypeCertificate {
|
||||||
return nil, rest, certerr.ParsingError(
|
return nil, rest, certerr.ParsingError(
|
||||||
certerr.ErrorSourceCertificate,
|
certerr.ErrorSourceCertificate,
|
||||||
certerr.ErrInvalidPEMType(p.Type, "CERTIFICATE"),
|
certerr.ErrInvalidPEMType(p.Type, pemTypeCertificate),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,3 +113,98 @@ func PoolFromBytes(certBytes []byte) (*x509.CertPool, error) {
|
|||||||
|
|
||||||
return pool, nil
|
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")
|
||||||
|
}
|
||||||
|
|||||||
@@ -249,11 +249,6 @@ func showBasicConstraints(cert *x509.Certificate) {
|
|||||||
fmt.Fprintln(os.Stdout)
|
fmt.Fprintln(os.Stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
dateFormat string
|
|
||||||
showHash bool // if true, print a SHA256 hash of the certificate's Raw field
|
|
||||||
)
|
|
||||||
|
|
||||||
func wrapPrint(text string, indent int) {
|
func wrapPrint(text string, indent int) {
|
||||||
tabs := ""
|
tabs := ""
|
||||||
var tabsSb140 strings.Builder
|
var tabsSb140 strings.Builder
|
||||||
@@ -265,11 +260,12 @@ func wrapPrint(text string, indent int) {
|
|||||||
fmt.Fprintf(os.Stdout, tabs+"%s\n", wrap(text, indent))
|
fmt.Fprintf(os.Stdout, tabs+"%s\n", wrap(text, indent))
|
||||||
}
|
}
|
||||||
|
|
||||||
func DisplayCert(w io.Writer, cert *x509.Certificate) {
|
func DisplayCert(w io.Writer, cert *x509.Certificate, showHash bool) {
|
||||||
fmt.Fprintln(w, "CERTIFICATE")
|
fmt.Fprintln(w, "CERTIFICATE")
|
||||||
if showHash {
|
if showHash {
|
||||||
fmt.Fprintln(w, wrap(fmt.Sprintf("SHA256: %x", sha256.Sum256(cert.Raw)), 0))
|
fmt.Fprintln(w, wrap(fmt.Sprintf("SHA256: %x", sha256.Sum256(cert.Raw)), 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(w, wrap("Subject: "+DisplayName(cert.Subject), 0))
|
fmt.Fprintln(w, wrap("Subject: "+DisplayName(cert.Subject), 0))
|
||||||
fmt.Fprintln(w, wrap("Issuer: "+DisplayName(cert.Issuer), 0))
|
fmt.Fprintln(w, wrap("Issuer: "+DisplayName(cert.Issuer), 0))
|
||||||
fmt.Fprintf(w, "\tSignature algorithm: %s / %s\n", sigAlgoPK(cert.SignatureAlgorithm),
|
fmt.Fprintf(w, "\tSignature algorithm: %s / %s\n", sigAlgoPK(cert.SignatureAlgorithm),
|
||||||
@@ -285,8 +281,8 @@ func DisplayCert(w io.Writer, cert *x509.Certificate) {
|
|||||||
fmt.Fprintf(w, "\t%s\n", wrap("SKI: "+dumpHex(cert.SubjectKeyId), 1))
|
fmt.Fprintf(w, "\t%s\n", wrap("SKI: "+dumpHex(cert.SubjectKeyId), 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
wrapPrint("Valid from: "+cert.NotBefore.Format(dateFormat), 1)
|
wrapPrint("Valid from: "+cert.NotBefore.Format(lib.DateShortFormat), 1)
|
||||||
fmt.Fprintf(w, "\t until: %s\n", cert.NotAfter.Format(dateFormat))
|
fmt.Fprintf(w, "\t until: %s\n", cert.NotAfter.Format(lib.DateShortFormat))
|
||||||
fmt.Fprintf(w, "\tKey usages: %s\n", keyUsages(cert.KeyUsage))
|
fmt.Fprintf(w, "\tKey usages: %s\n", keyUsages(cert.KeyUsage))
|
||||||
|
|
||||||
if len(cert.ExtKeyUsage) > 0 {
|
if len(cert.ExtKeyUsage) > 0 {
|
||||||
|
|||||||
@@ -75,6 +75,12 @@ var DelegationExtension = pkix.Extension{
|
|||||||
Value: []byte{0x05, 0x00}, // ASN.1 NULL
|
Value: []byte{0x05, 0x00}, // ASN.1 NULL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
pemTypeCertificate = "CERTIFICATE"
|
||||||
|
pemTypeCertificateRequest = "CERTIFICATE REQUEST"
|
||||||
|
pemTypePrivateKey = "PRIVATE KEY"
|
||||||
|
)
|
||||||
|
|
||||||
// InclusiveDate returns the time.Time representation of a date - 1
|
// InclusiveDate returns the time.Time representation of a date - 1
|
||||||
// nanosecond. This allows time.After to be used inclusively.
|
// nanosecond. This allows time.After to be used inclusively.
|
||||||
func InclusiveDate(year int, month time.Month, day int) time.Time {
|
func InclusiveDate(year int, month time.Month, day int) time.Time {
|
||||||
@@ -246,7 +252,7 @@ func EncodeCertificatesPEM(certs []*x509.Certificate) []byte {
|
|||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
for _, cert := range certs {
|
for _, cert := range certs {
|
||||||
if err := pem.Encode(&buffer, &pem.Block{
|
if err := pem.Encode(&buffer, &pem.Block{
|
||||||
Type: "CERTIFICATE",
|
Type: pemTypeCertificate,
|
||||||
Bytes: cert.Raw,
|
Bytes: cert.Raw,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
3
cmd/cert-bundler/testdata/bundle.yaml
vendored
3
cmd/cert-bundler/testdata/bundle.yaml
vendored
@@ -12,6 +12,7 @@ chains:
|
|||||||
include_single: true
|
include_single: true
|
||||||
include_individual: true
|
include_individual: true
|
||||||
manifest: true
|
manifest: true
|
||||||
|
encoding: pemcrt
|
||||||
formats:
|
formats:
|
||||||
- zip
|
- zip
|
||||||
- tgz
|
- tgz
|
||||||
@@ -53,4 +54,4 @@ chains:
|
|||||||
manifest: false
|
manifest: false
|
||||||
encoding: both
|
encoding: both
|
||||||
formats:
|
formats:
|
||||||
- zip
|
- zip
|
||||||
|
|||||||
@@ -35,12 +35,12 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if config.leafOnly {
|
if config.leafOnly {
|
||||||
dump.DisplayCert(os.Stdout, certs[0])
|
dump.DisplayCert(os.Stdout, certs[0], config.showHash)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range certs {
|
for i := range certs {
|
||||||
dump.DisplayCert(os.Stdout, certs[i])
|
dump.DisplayCert(os.Stdout, certs[i], config.showHash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user