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:
parent
873eb4879b
commit
017a6b0a10
5 changed files with 140 additions and 74 deletions
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"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/filter"
|
||||||
"github.com/csaf-poc/csaf_distribution/v2/internal/models"
|
"github.com/csaf-poc/csaf_distribution/v2/internal/models"
|
||||||
"github.com/csaf-poc/csaf_distribution/v2/internal/options"
|
"github.com/csaf-poc/csaf_distribution/v2/internal/options"
|
||||||
|
|
@ -29,17 +30,18 @@ const (
|
||||||
type config struct {
|
type config struct {
|
||||||
Output string `short:"o" long:"output" description:"File name of the generated report" value-name:"REPORT-FILE" toml:"output"`
|
Output string `short:"o" long:"output" description:"File name of the generated report" value-name:"REPORT-FILE" toml:"output"`
|
||||||
//lint:ignore SA5008 We are using choice twice: json, html.
|
//lint:ignore SA5008 We are using choice twice: json, html.
|
||||||
Format outputFormat `short:"f" long:"format" choice:"json" choice:"html" description:"Format of report" toml:"format"`
|
Format outputFormat `short:"f" long:"format" choice:"json" choice:"html" description:"Format of report" toml:"format"`
|
||||||
Insecure bool `long:"insecure" description:"Do not check TLS certificates from provider" toml:"insecure"`
|
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"`
|
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"`
|
ClientKey *string `long:"client-key" description:"TLS client private key file (PEM encoded data)" value-name:"KEY-FILE" toml:"client_key"`
|
||||||
Version bool `long:"version" description:"Display version of the binary" toml:"-"`
|
ClientPassphrase *string `long:"client-passphrase" description:"Optional passphrase for the client certificate" value-name:"PASSPHRASE" toml:"client_passphrase"`
|
||||||
Verbose bool `long:"verbose" short:"v" description:"Verbose output" toml:"verbose"`
|
Version bool `long:"version" description:"Display version of the binary" toml:"-"`
|
||||||
Rate *float64 `long:"rate" short:"r" description:"The average upper limit of https operations per second (defaults to unlimited)" toml:"rate"`
|
Verbose bool `long:"verbose" short:"v" description:"Verbose output" toml:"verbose"`
|
||||||
Years *uint `long:"years" short:"y" description:"Number of years to look back from now" value-name:"YEARS" toml:"years"`
|
Rate *float64 `long:"rate" short:"r" description:"The average upper limit of https operations per second (defaults to unlimited)" toml:"rate"`
|
||||||
Range *models.TimeRange `long:"timerange" short:"t" description:"RANGE of time from which advisories to download" value-name:"RANGE" toml:"timerange"`
|
Years *uint `long:"years" short:"y" description:"Number of years to look back from now" value-name:"YEARS" toml:"years"`
|
||||||
IgnorePattern []string `long:"ignorepattern" short:"i" description:"Dont download files if there URLs match any of the given PATTERNs" value-name:"PATTERN" toml:"ignorepattern"`
|
Range *models.TimeRange `long:"timerange" short:"t" description:"RANGE of time from which advisories to download" value-name:"RANGE" toml:"timerange"`
|
||||||
ExtraHeader http.Header `long:"header" short:"H" description:"One or more extra HTTP header fields" toml:"header"`
|
IgnorePattern []string `long:"ignorepattern" short:"i" description:"Dont download files if there URLs match any of the given PATTERNs" value-name:"PATTERN" toml:"ignorepattern"`
|
||||||
|
ExtraHeader http.Header `long:"header" short:"H" description:"One or more extra HTTP header fields" toml:"header"`
|
||||||
|
|
||||||
RemoteValidator string `long:"validator" description:"URL to validate documents remotely" value-name:"URL" toml:"validator"`
|
RemoteValidator string `long:"validator" description:"URL to validate documents remotely" value-name:"URL" toml:"validator"`
|
||||||
RemoteValidatorCache string `long:"validatorcache" description:"FILE to cache remote validations" value-name:"FILE" toml:"validator_cache"`
|
RemoteValidatorCache string `long:"validatorcache" description:"FILE to cache remote validations" value-name:"FILE" toml:"validator_cache"`
|
||||||
|
|
@ -139,19 +141,12 @@ func (cfg *config) compileIgnorePatterns() error {
|
||||||
|
|
||||||
// prepareCertificates loads the client side certificates used by the HTTP client.
|
// prepareCertificates loads the client side certificates used by the HTTP client.
|
||||||
func (cfg *config) prepareCertificates() error {
|
func (cfg *config) prepareCertificates() error {
|
||||||
|
cert, err := certs.LoadCertificate(
|
||||||
switch hasCert, hasKey := cfg.ClientCert != nil, cfg.ClientKey != nil; {
|
cfg.ClientCert, cfg.ClientKey, cfg.ClientPassphrase)
|
||||||
|
if err != nil {
|
||||||
case hasCert && !hasKey || !hasCert && hasKey:
|
return err
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cfg.clientCerts = []tls.Certificate{cert}
|
|
||||||
}
|
}
|
||||||
|
cfg.clientCerts = cert
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"github.com/ProtonMail/gopenpgp/v2/constants"
|
"github.com/ProtonMail/gopenpgp/v2/constants"
|
||||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
"github.com/csaf-poc/csaf_distribution/v2/csaf"
|
"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/csaf-poc/csaf_distribution/v2/util"
|
||||||
"github.com/jessevdk/go-flags"
|
"github.com/jessevdk/go-flags"
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
|
|
@ -43,11 +44,12 @@ type options struct {
|
||||||
ExternalSigned bool `short:"x" long:"external-signed" description:"CSAF files are signed externally. Assumes .asc files beside CSAF files."`
|
ExternalSigned bool `short:"x" long:"external-signed" description:"CSAF files are signed externally. Assumes .asc files beside CSAF files."`
|
||||||
NoSchemaCheck bool `short:"s" long:"no-schema-check" description:"Do not check files against CSAF JSON schema locally."`
|
NoSchemaCheck bool `short:"s" long:"no-schema-check" description:"Do not check files against CSAF JSON schema locally."`
|
||||||
|
|
||||||
Key *string `short:"k" long:"key" description:"OpenPGP key to sign the CSAF files" value-name:"KEY-FILE"`
|
Key *string `short:"k" long:"key" description:"OpenPGP key to sign the CSAF files" value-name:"KEY-FILE"`
|
||||||
Password *string `short:"p" long:"password" description:"Authentication password for accessing the CSAF provider" value-name:"PASSWORD"`
|
Password *string `short:"p" long:"password" description:"Authentication password for accessing the CSAF provider" value-name:"PASSWORD"`
|
||||||
Passphrase *string `short:"P" long:"passphrase" description:"Passphrase to unlock the OpenPGP key" value-name:"PASSPHRASE"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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 {
|
func (o *options) prepare() error {
|
||||||
// Load client certs.
|
// Load client certs.
|
||||||
switch hasCert, hasKey := o.ClientCert != nil, o.ClientKey != nil; {
|
cert, err := certs.LoadCertificate(
|
||||||
|
o.ClientCert, o.ClientKey, o.ClientPassphrase)
|
||||||
case hasCert && !hasKey || !hasCert && hasKey:
|
if err != nil {
|
||||||
return errors.New("both client-key and client-cert options must be set for the authentication")
|
return err
|
||||||
|
|
||||||
case hasCert:
|
|
||||||
cert, err := tls.LoadX509KeyPair(*o.ClientCert, *o.ClientKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
o.clientCerts = []tls.Certificate{cert}
|
|
||||||
}
|
}
|
||||||
|
o.clientCerts = cert
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,25 +7,26 @@ Usage:
|
||||||
csaf_checker [OPTIONS] domain...
|
csaf_checker [OPTIONS] domain...
|
||||||
|
|
||||||
Application Options:
|
Application Options:
|
||||||
-o, --output=REPORT-FILE File name of the generated report
|
-o, --output=REPORT-FILE File name of the generated report
|
||||||
-f, --format=[json|html] Format of report (default: json)
|
-f, --format=[json|html] Format of report (default: json)
|
||||||
--insecure Do not check TLS certificates from provider
|
--insecure Do not check TLS certificates from provider
|
||||||
--client-cert=CERT-FILE TLS client certificate file (PEM encoded data)
|
--client-cert=CERT-FILE TLS client certificate file (PEM encoded data)
|
||||||
--client-key=KEY-FILE TLS client private key file (PEM encoded data)
|
--client-key=KEY-FILE TLS client private key file (PEM encoded data)
|
||||||
--version Display version of the binary
|
--client-passphrase=PASSPHRASE Optional passphrase for the client certificate
|
||||||
-v, --verbose Verbose output
|
--version Display version of the binary
|
||||||
-r, --rate= The average upper limit of https operations per second (defaults to unlimited)
|
-v, --verbose Verbose output
|
||||||
-y, --years=YEARS Number of years to look back from now
|
-r, --rate= The average upper limit of https operations per second (defaults to unlimited)
|
||||||
-t, --timerange=RANGE RANGE of time from which advisories to download
|
-y, --years=YEARS Number of years to look back from now
|
||||||
-i, --ignorepattern=PATTERN Dont download files if there URLs match any of the given PATTERNs
|
-t, --timerange=RANGE RANGE of time from which advisories to download
|
||||||
-H, --header= One or more extra HTTP header fields
|
-i, --ignorepattern=PATTERN Dont download files if there URLs match any of the given PATTERNs
|
||||||
--validator=URL URL to validate documents remotely
|
-H, --header= One or more extra HTTP header fields
|
||||||
--validatorcache=FILE FILE to cache remote validations
|
--validator=URL URL to validate documents remotely
|
||||||
--validatorpreset= One or more presets to validate remotely (default: [mandatory])
|
--validatorcache=FILE FILE to cache remote validations
|
||||||
-c, --config=TOML-FILE Path to config TOML file
|
--validatorpreset= One or more presets to validate remotely (default: [mandatory])
|
||||||
|
-c, --config=TOML-FILE Path to config TOML file
|
||||||
|
|
||||||
Help Options:
|
Help Options:
|
||||||
-h, --help Show this help message
|
-h, --help Show this help message
|
||||||
```
|
```
|
||||||
|
|
||||||
Will check all given _domains_, by trying each as a CSAF provider.
|
Will check all given _domains_, by trying each as a CSAF provider.
|
||||||
|
|
@ -42,19 +43,20 @@ csaf_checker.toml
|
||||||
with `~` expanding to `$HOME` on unixoid systems and `%HOMEPATH` on Windows systems.
|
with `~` expanding to `$HOME` on unixoid systems and `%HOMEPATH` on Windows systems.
|
||||||
Supported options in config files:
|
Supported options in config files:
|
||||||
```
|
```
|
||||||
output = ""
|
output = ""
|
||||||
format = "json"
|
format = "json"
|
||||||
insecure = false
|
insecure = false
|
||||||
# client_cert # not set by default
|
# client_cert # not set by default
|
||||||
# client_key # not set by default
|
# client_key # not set by default
|
||||||
verbose = false
|
# client_passphrase # not set by default
|
||||||
# rate # not set by default
|
verbose = false
|
||||||
# years # not set by default
|
# rate # not set by default
|
||||||
# timerange # not set by default
|
# years # not set by default
|
||||||
# header # not set by default
|
# timerange # not set by default
|
||||||
# validator # not set by default
|
# header # not set by default
|
||||||
# validatorcache # not set by default
|
# validator # not set by default
|
||||||
validatorpreset = ["mandatory"]
|
# validatorcache # not set by default
|
||||||
|
validatorpreset = ["mandatory"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Usage example:
|
Usage example:
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,18 @@
|
||||||
|
|
||||||
Application Options:
|
Application Options:
|
||||||
-a, --action=[upload|create] Action to perform (default: upload)
|
-a, --action=[upload|create] Action to perform (default: upload)
|
||||||
-u, --url=URL URL of the CSAF provider (default:
|
-u, --url=URL URL of the CSAF provider (default: https://localhost/cgi-bin/csaf_provider.go)
|
||||||
https://localhost/cgi-bin/csaf_provider.go)
|
|
||||||
-t, --tlp=[csaf|white|green|amber|red] TLP of the feed (default: csaf)
|
-t, --tlp=[csaf|white|green|amber|red] TLP of the feed (default: csaf)
|
||||||
-x, --external-signed CSAF files are signed externally. Assumes .asc files
|
-x, --external-signed CSAF files are signed externally. Assumes .asc files beside CSAF files.
|
||||||
beside CSAF files.
|
|
||||||
-s, --no-schema-check Do not check files against CSAF JSON schema locally.
|
-s, --no-schema-check Do not check files against CSAF JSON schema locally.
|
||||||
-k, --key=KEY-FILE OpenPGP key to sign the CSAF files
|
-k, --key=KEY-FILE OpenPGP key to sign the CSAF files
|
||||||
-p, --password=PASSWORD Authentication password for accessing the CSAF provider
|
-p, --password=PASSWORD Authentication password for accessing the CSAF provider
|
||||||
-P, --passphrase=PASSPHRASE Passphrase to unlock the OpenPGP key
|
-P, --passphrase=PASSPHRASE Passphrase to unlock the OpenPGP key
|
||||||
--client-cert=CERT-FILE.crt TLS client certificate file (PEM encoded data)
|
--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-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, --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
|
--insecure Do not check TLS certificates from provider
|
||||||
-c, --config=INI-FILE Path to config ini file
|
-c, --config=INI-FILE Path to config ini file
|
||||||
--version Display version of the binary
|
--version Display version of the binary
|
||||||
|
|
|
||||||
74
internal/certs/certs.go
Normal file
74
internal/certs/certs.go
Normal 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
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue