Skip to content

Commit

Permalink
Add Export functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
hdecarne committed Oct 19, 2024
1 parent f919ebd commit 4580798
Show file tree
Hide file tree
Showing 5 changed files with 429 additions and 22 deletions.
2 changes: 1 addition & 1 deletion certs/acme_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func loadAndPrepareACMEConfig(t *testing.T, configPath string, tempDir string) *
certificates, err := certs.ServerCertificates("tcp", providerUrl.Host)
require.NoError(t, err)
certificateFile := filepath.Join(tempDir, provider.Name+".pem")
err = certs.WriteCertificatesPEM(certificateFile, certificates, 0600)
err = certs.WriteCertificatesPEMFile(certificateFile, certificates, 0600)
require.NoError(t, err)
certificateFiles = append(certificateFiles, certificateFile)
}
Expand Down
255 changes: 248 additions & 7 deletions certs/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,35 @@
package certs

import (
"archive/zip"
"crypto"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"net/http"
"os"

"github.com/hdecarne-github/go-certstore/keys"
"software.sslmate.com/src/go-pkcs12"
)

// ReadCertificates reads X.509 certificates from the given file.
func ReadCertificates(filename string) ([]*x509.Certificate, error) {
// ReadCertificates reads X.509 certificates from the given [io.Reader].
func ReadCertificates(in io.Reader) ([]*x509.Certificate, error) {
bytes, err := io.ReadAll(in)
if err != nil {
return nil, fmt.Errorf("failed to read certificates (cause: %w)", err)
}
decoded, err := decodeCertificates(bytes)
if err != nil {
return nil, fmt.Errorf("failed to decode certificates (cause: %w)", err)
}
return decoded, nil
}

// ReadCertificatesFile reads X.509 certificates from the given file name.
func ReadCertificatesFile(filename string) ([]*x509.Certificate, error) {
bytes, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("failed to read certificates from file '%s' (cause: %w)", filename, err)
Expand Down Expand Up @@ -49,8 +67,20 @@ func decodeCertificates(bytes []byte) ([]*x509.Certificate, error) {
return decoded, nil
}

// WriteCertificatesPEM writes X.509 certificates in PEM format to the given file.
func WriteCertificatesPEM(filename string, certificates []*x509.Certificate, perm os.FileMode) error {
// WriteCertificatesPEM writes X.509 certificates in PEM format to the given [io.Writer].
func WriteCertificatesPEM(out io.Writer, certificates []*x509.Certificate) error {
encoded := encodeCertificatesPEM(certificates)
_, err := out.Write(encoded)
return err
}

// WriteCertificatesPEMFile writes X.509 certificates in PEM format to the given file name.
func WriteCertificatesPEMFile(filename string, certificates []*x509.Certificate, perm os.FileMode) error {
encoded := encodeCertificatesPEM(certificates)
return os.WriteFile(filename, encoded, perm)
}

func encodeCertificatesPEM(certificates []*x509.Certificate) []byte {
encoded := make([]byte, 0)
for _, certificate := range certificates {
block := &pem.Block{
Expand All @@ -59,16 +89,50 @@ func WriteCertificatesPEM(filename string, certificates []*x509.Certificate, per
}
encoded = append(encoded, pem.EncodeToMemory(block)...)
}
return encoded
}

func encodeKeyPEM(key crypto.PrivateKey) ([]byte, error) {
encodedKey, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
algorithm, _ := keys.AlgorithmFromKey(keys.KeyFromPrivate(key).Public())
return nil, fmt.Errorf("failed to marshal private key of type '%s' (cause: %w)", algorithm, err)
}
block := &pem.Block{
Type: "PRIVATE KEY",
Bytes: encodedKey,
}
return pem.EncodeToMemory(block), nil
}

// WriteCertificatesDER writes X.509 certificates in DER format to the given [io.Writer].
func WriteCertificatesDER(out io.Writer, certificates []*x509.Certificate) error {
encoded := encodeCertificatesDER(certificates)
_, err := out.Write(encoded)
return err
}

// WriteCertificatesDERFile writes X.509 certificates in DER format to the given file.
func WriteCertificatesDERFile(filename string, certificates []*x509.Certificate, perm os.FileMode) error {
encoded := encodeCertificatesDER(certificates)
return os.WriteFile(filename, encoded, perm)
}

// WriteCertificatesDER writes X.509 certificates in DER format to the given file.
func WriteCertificatesDER(filename string, certificates []*x509.Certificate, perm os.FileMode) error {
func encodeCertificatesDER(certificates []*x509.Certificate) []byte {
encoded := make([]byte, 0)
for _, certificate := range certificates {
encoded = append(encoded, certificate.Raw...)
}
return os.WriteFile(filename, encoded, perm)
return encoded
}

func encodeKeyDER(key crypto.PrivateKey) ([]byte, error) {
encoded, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
algorithm, _ := keys.AlgorithmFromKey(keys.KeyFromPrivate(key).Public())
return nil, fmt.Errorf("failed to marshal private key of type '%s' (cause: %w)", algorithm, err)
}
return encoded, nil
}

// FetchCertificates fetches X.509 certificates from the given URL.
Expand Down Expand Up @@ -130,3 +194,180 @@ func verifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certifica
err.Err = fmt.Errorf("%d peer certifcates received", len(err.UnverifiedCertificates))
return &err
}

func ExportPEM(out io.Writer, certificate *x509.Certificate, chain []*x509.Certificate, key crypto.PrivateKey) error {
zip := zip.NewWriter(out)
if certificate != nil {
err := exportCertificatePEM(zip, certificate)
if err != nil {
return nil
}
}
if len(chain) > 0 {
err := exportChainsPEM(zip, chain)
if err != nil {
return nil
}
}
if key != nil {
err := exportKeyPEM(zip, key)
if err != nil {
return nil
}
}
return zip.Flush()
}

func exportCertificatePEM(zip *zip.Writer, certificate *x509.Certificate) error {
file, err := zip.Create("cert.pem")
if err != nil {
return fmt.Errorf("failed to create cert.pem file (cause: %w)", err)
}
err = WriteCertificatesPEM(file, []*x509.Certificate{certificate})
if err != nil {
return fmt.Errorf("failed to wirte cert.pem file (cause: %w)", err)
}
return nil
}

func exportChainsPEM(zip *zip.Writer, chain []*x509.Certificate) error {
if IsRoot(chain[len(chain)-1]) {
err := exportChainPEM(zip, "fullchain.pem", chain)
if err != nil {
return nil
}
if len(chain) > 1 {
err := exportChainPEM(zip, "chain.pem", chain[:len(chain)-1])
if err != nil {
return nil
}
}
} else {
err := exportChainPEM(zip, "chain.pem", chain)
if err != nil {
return nil
}
}
return nil
}

func exportChainPEM(zip *zip.Writer, name string, chain []*x509.Certificate) error {
file, err := zip.Create(name)
if err != nil {
return fmt.Errorf("failed to create %s file (cause: %w)", name, err)
}
err = WriteCertificatesPEM(file, chain)
if err != nil {
return fmt.Errorf("failed to write %s file (cause: %w)", name, err)
}
return nil
}

func exportKeyPEM(zip *zip.Writer, key crypto.PrivateKey) error {
file, err := zip.Create("key.pem")
if err != nil {
return fmt.Errorf("failed to create key.pem file (cause: %w)", err)
}
encoded, err := encodeKeyPEM(key)
if err != nil {
return err
}
_, err = file.Write(encoded)
if err != nil {
return fmt.Errorf("failed to write key.pem file (cause: %w)", err)
}
return nil
}

func ExportDER(out io.Writer, certificate *x509.Certificate, chain []*x509.Certificate, key crypto.PrivateKey) error {
zip := zip.NewWriter(out)
if certificate != nil {
err := exportCertificateDER(zip, certificate)
if err != nil {
return nil
}
}
if len(chain) > 0 {
err := exportChainsDER(zip, chain)
if err != nil {
return nil
}
}
if key != nil {
err := exportKeyDER(zip, key)
if err != nil {
return nil
}
}
return zip.Flush()
}

func exportCertificateDER(zip *zip.Writer, certificate *x509.Certificate) error {
file, err := zip.Create("cert.der")
if err != nil {
return fmt.Errorf("failed to create cert.der file (cause: %w)", err)
}
err = WriteCertificatesDER(file, []*x509.Certificate{certificate})
if err != nil {
return fmt.Errorf("failed to wirte cert.der file (cause: %w)", err)
}
return nil
}

func exportChainsDER(zip *zip.Writer, chain []*x509.Certificate) error {
if IsRoot(chain[len(chain)-1]) {
err := exportChainDER(zip, "fullchain.der", chain)
if err != nil {
return nil
}
if len(chain) > 1 {
err := exportChainDER(zip, "chain.der", chain[:len(chain)-1])
if err != nil {
return nil
}
}
} else {
err := exportChainDER(zip, "chain.der", chain)
if err != nil {
return nil
}
}
return nil
}

func exportChainDER(zip *zip.Writer, name string, chain []*x509.Certificate) error {
file, err := zip.Create(name)
if err != nil {
return fmt.Errorf("failed to create %s file (cause: %w)", name, err)
}
err = WriteCertificatesDER(file, chain)
if err != nil {
return fmt.Errorf("failed to write %s file (cause: %w)", name, err)
}
return nil
}

func exportKeyDER(zip *zip.Writer, key crypto.PrivateKey) error {
file, err := zip.Create("key.der")
if err != nil {
return fmt.Errorf("failed to create key.der file (cause: %w)", err)
}
encoded, err := encodeKeyDER(key)
if err != nil {
return err
}
_, err = file.Write(encoded)
if err != nil {
return fmt.Errorf("failed to write key.der file (cause: %w)", err)
}
return nil
}

func ExportPKCS12(out io.Writer, certificate *x509.Certificate, chain []*x509.Certificate, key crypto.PrivateKey, password string) error {
encoded, err := pkcs12.Modern.Encode(key, certificate, chain, password)
if err != nil {
return fmt.Errorf("failed to encode PKCS12 (cause: %w)", err)
}
_, err = out.Write(encoded)
return err
}
24 changes: 12 additions & 12 deletions certs/io_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,48 +13,48 @@ import (
"github.com/stretchr/testify/require"
)

func TestReadPEMCertificates(t *testing.T) {
certificates, err := certs.ReadCertificates("./testdata/isrgrootx1.pem")
func TestReadPEMCertificatesFile(t *testing.T) {
certificates, err := certs.ReadCertificatesFile("./testdata/isrgrootx1.pem")
require.NoError(t, err)
require.NotNil(t, certificates)
require.Equal(t, 1, len(certificates))
}

func TestReadDERCertificates(t *testing.T) {
certificates, err := certs.ReadCertificates("./testdata/isrgrootx1.der")
func TestReadDERCertificatesFile(t *testing.T) {
certificates, err := certs.ReadCertificatesFile("./testdata/isrgrootx1.der")
require.NoError(t, err)
require.NotNil(t, certificates)
require.Equal(t, 1, len(certificates))
}

func TestWritePEMCertificate(t *testing.T) {
certificates, err := certs.ReadCertificates("./testdata/isrgrootx1.pem")
func TestWritePEMCertificateFile(t *testing.T) {
certificates, err := certs.ReadCertificatesFile("./testdata/isrgrootx1.pem")
require.NoError(t, err)
file, err := os.CreateTemp("", "PEMCertificate*")
require.NoError(t, err)
defer func() {
os.Remove(file.Name())
}()
file.Close()
err = certs.WriteCertificatesPEM(file.Name(), certificates, 0600)
err = certs.WriteCertificatesPEMFile(file.Name(), certificates, 0600)
require.NoError(t, err)
certificates2, err := certs.ReadCertificates(file.Name())
certificates2, err := certs.ReadCertificatesFile(file.Name())
require.NoError(t, err)
require.Equal(t, certificates, certificates2)
}

func TestWriteDERCertificate(t *testing.T) {
certificates, err := certs.ReadCertificates("./testdata/isrgrootx1.der")
func TestWriteDERCertificateFile(t *testing.T) {
certificates, err := certs.ReadCertificatesFile("./testdata/isrgrootx1.der")
require.NoError(t, err)
file, err := os.CreateTemp("", "DERCertificate*")
require.NoError(t, err)
defer func() {
os.Remove(file.Name())
}()
file.Close()
err = certs.WriteCertificatesDER(file.Name(), certificates, 0600)
err = certs.WriteCertificatesDERFile(file.Name(), certificates, 0600)
require.NoError(t, err)
certificates2, err := certs.ReadCertificates(file.Name())
certificates2, err := certs.ReadCertificatesFile(file.Name())
require.NoError(t, err)
require.Equal(t, certificates, certificates2)
}
Expand Down
Loading

0 comments on commit 4580798

Please sign in to comment.