1
0
Fork 0
mirror of https://github.com/gocsaf/csaf.git synced 2025-12-22 11:55:40 +01:00

Move cert handling into library and add option passphrase. Adjust uploader and checker.

This commit is contained in:
Sascha L. Teichmann 2023-08-02 21:02:58 +02:00
parent 873eb4879b
commit 017a6b0a10
5 changed files with 140 additions and 74 deletions

View file

@ -14,6 +14,7 @@ import (
"fmt"
"net/http"
"github.com/csaf-poc/csaf_distribution/v2/internal/certs"
"github.com/csaf-poc/csaf_distribution/v2/internal/filter"
"github.com/csaf-poc/csaf_distribution/v2/internal/models"
"github.com/csaf-poc/csaf_distribution/v2/internal/options"
@ -33,6 +34,7 @@ type config struct {
Insecure bool `long:"insecure" description:"Do not check TLS certificates from provider" toml:"insecure"`
ClientCert *string `long:"client-cert" description:"TLS client certificate file (PEM encoded data)" value-name:"CERT-FILE" toml:"client_cert"`
ClientKey *string `long:"client-key" description:"TLS client private key file (PEM encoded data)" value-name:"KEY-FILE" toml:"client_key"`
ClientPassphrase *string `long:"client-passphrase" description:"Optional passphrase for the client certificate" value-name:"PASSPHRASE" toml:"client_passphrase"`
Version bool `long:"version" description:"Display version of the binary" toml:"-"`
Verbose bool `long:"verbose" short:"v" description:"Verbose output" toml:"verbose"`
Rate *float64 `long:"rate" short:"r" description:"The average upper limit of https operations per second (defaults to unlimited)" toml:"rate"`
@ -139,19 +141,12 @@ func (cfg *config) compileIgnorePatterns() error {
// prepareCertificates loads the client side certificates used by the HTTP client.
func (cfg *config) prepareCertificates() error {
switch hasCert, hasKey := cfg.ClientCert != nil, cfg.ClientKey != nil; {
case hasCert && !hasKey || !hasCert && hasKey:
return errors.New("both client-key and client-cert options must be set for the authentication")
case hasCert:
cert, err := tls.LoadX509KeyPair(*cfg.ClientCert, *cfg.ClientKey)
cert, err := certs.LoadCertificate(
cfg.ClientCert, cfg.ClientKey, cfg.ClientPassphrase)
if err != nil {
return err
}
cfg.clientCerts = []tls.Certificate{cert}
}
cfg.clientCerts = cert
return nil
}

View file

@ -28,6 +28,7 @@ import (
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/csaf-poc/csaf_distribution/v2/csaf"
"github.com/csaf-poc/csaf_distribution/v2/internal/certs"
"github.com/csaf-poc/csaf_distribution/v2/util"
"github.com/jessevdk/go-flags"
"github.com/mitchellh/go-homedir"
@ -48,6 +49,7 @@ type options struct {
Passphrase *string `short:"P" long:"passphrase" description:"Passphrase to unlock the OpenPGP key" value-name:"PASSPHRASE"`
ClientCert *string `long:"client-cert" description:"TLS client certificate file (PEM encoded data)" value-name:"CERT-FILE.crt"`
ClientKey *string `long:"client-key" description:"TLS client private key file (PEM encoded data)" value-name:"KEY-FILE.pem"`
ClientPassphrase *string `long:"client-passphrase" description:"Optional passphrase for the client certificate" value-name:"PASSPHRASE"`
PasswordInteractive bool `short:"i" long:"password-interactive" description:"Enter password interactively" no-ini:"true"`
PassphraseInteractive bool `short:"I" long:"passphrase-interactive" description:"Enter OpenPGP key passphrase interactively" no-ini:"true"`
@ -75,18 +77,12 @@ var iniPaths = []string{
func (o *options) prepare() error {
// Load client certs.
switch hasCert, hasKey := o.ClientCert != nil, o.ClientKey != nil; {
case hasCert && !hasKey || !hasCert && hasKey:
return errors.New("both client-key and client-cert options must be set for the authentication")
case hasCert:
cert, err := tls.LoadX509KeyPair(*o.ClientCert, *o.ClientKey)
cert, err := certs.LoadCertificate(
o.ClientCert, o.ClientKey, o.ClientPassphrase)
if err != nil {
return err
}
o.clientCerts = []tls.Certificate{cert}
}
o.clientCerts = cert
return nil
}

View file

@ -12,6 +12,7 @@ Application Options:
--insecure Do not check TLS certificates from provider
--client-cert=CERT-FILE TLS client certificate file (PEM encoded data)
--client-key=KEY-FILE TLS client private key file (PEM encoded data)
--client-passphrase=PASSPHRASE Optional passphrase for the client certificate
--version Display version of the binary
-v, --verbose Verbose output
-r, --rate= The average upper limit of https operations per second (defaults to unlimited)
@ -47,6 +48,7 @@ format = "json"
insecure = false
# client_cert # not set by default
# client_key # not set by default
# client_passphrase # not set by default
verbose = false
# rate # not set by default
# years # not set by default

View file

@ -7,19 +7,18 @@
Application Options:
-a, --action=[upload|create] Action to perform (default: upload)
-u, --url=URL URL of the CSAF provider (default:
https://localhost/cgi-bin/csaf_provider.go)
-u, --url=URL URL of the CSAF provider (default: https://localhost/cgi-bin/csaf_provider.go)
-t, --tlp=[csaf|white|green|amber|red] TLP of the feed (default: csaf)
-x, --external-signed CSAF files are signed externally. Assumes .asc files
beside CSAF files.
-x, --external-signed CSAF files are signed externally. Assumes .asc files beside CSAF files.
-s, --no-schema-check Do not check files against CSAF JSON schema locally.
-k, --key=KEY-FILE OpenPGP key to sign the CSAF files
-p, --password=PASSWORD Authentication password for accessing the CSAF provider
-P, --passphrase=PASSPHRASE Passphrase to unlock the OpenPGP key
--client-cert=CERT-FILE.crt TLS client certificate file (PEM encoded data)
--client-key=KEY-FILE.pem TLS client private key file (PEM encoded data)
--client-passphrase=PASSPHRASE Optional passphrase for the client certificate
-i, --password-interactive Enter password interactively
-I, --passphrase-interactive Enter passphrase interactively
-I, --passphrase-interactive Enter OpenPGP key passphrase interactively
--insecure Do not check TLS certificates from provider
-c, --config=INI-FILE Path to config ini file
--version Display version of the binary

74
internal/certs/certs.go Normal file
View file

@ -0,0 +1,74 @@
// This file is Free Software under the MIT License
// without warranty, see README.md and LICENSES/MIT.txt for details.
//
// SPDX-License-Identifier: MIT
//
// SPDX-FileCopyrightText: 2023 German Federal Office for Information Security (BSI) <https://www.bsi.bund.de>
// Software-Engineering: 2023 Intevation GmbH <https://intevation.de>
// Package certs implement helpers for the tools to handle client side certifacates.
package certs
import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"errors"
"os"
)
// LoadCertificate loads an client certificate from file with an optional passphrase.
// Returns nil if no certificate was loaded.
func LoadCertificate(certFile, keyFile, passphrase *string) ([]tls.Certificate, error) {
switch hasCert, hasKey := certFile != nil, keyFile != nil; {
case hasCert && !hasKey || !hasCert && hasKey:
return nil, errors.New(
"both client-key and client-cert options must be set for the authentication")
case hasCert:
// No passphrase
if passphrase == nil {
cert, err := tls.LoadX509KeyPair(*certFile, *keyFile)
if err != nil {
return nil, err
}
return []tls.Certificate{cert}, nil
}
// With passphrase
keyFile, err := os.ReadFile(*keyFile)
if err != nil {
return nil, err
}
keyBlock, _ := pem.Decode(keyFile)
//lint:ignore SA1019 This is insecure by design.
keyDER, err := x509.DecryptPEMBlock(keyBlock, []byte(*passphrase))
if err != nil {
return nil, err
}
// Update keyBlock with the plaintext bytes and clear the now obsolete
// headers.
keyBlock.Bytes = keyDER
keyBlock.Headers = nil
// Turn the key back into PEM format so we can leverage tls.X509KeyPair,
// which will deal with the intricacies of error handling, different key
// types, certificate chains, etc
keyPEM := pem.EncodeToMemory(keyBlock)
certPEMBlock, err := os.ReadFile(*certFile)
if err != nil {
return nil, err
}
cert, err := tls.X509KeyPair(certPEMBlock, keyPEM)
if err != nil {
return nil, err
}
return []tls.Certificate{cert}, nil
}
return nil, nil
}