API CHANGES:
* The struct tags for marshaling/unmarshaling on TlsFlat* have changed.

ADDED:
* Stubs for further chain processing/associations/correlation
This commit is contained in:
brent saner 2024-10-17 15:20:27 -04:00
parent 4cb0403e08
commit ef5ef16de3
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
5 changed files with 268 additions and 16 deletions

View File

@ -665,6 +665,34 @@ func ParseCA(certRaw []byte) (certPool *x509.CertPool, rootCerts []*x509.Certifi
return
}

/*
ParseDhParams parses PEM bytes and returns parsed DH parameters.

Concatenated PEM files are supported.

TODO: Currently not fully implemented; params will always be nil.
*/
/*
func ParseDhParams(dhRaw []byte) (params []*dhparam.DH, err error) {

var pemBlocks *PemBlocks

if dhRaw == nil || len(dhRaw) == 0 {
return
}
if pemBlocks, err = SplitPemBlocks(dhRaw); err != nil {
return
}
if pemBlocks == nil || len(*pemBlocks) == 0 {
return
}

// TODO

return
}
*/

/*
ParseLeafCert parses PEM bytes from a (client) certificate file, iterates over a slice of
crypto.PrivateKey (finding one that matches), and returns one (or more) tls.Certificate.
@ -767,6 +795,49 @@ func ParseLeafCert(certRaw []byte, keys []crypto.PrivateKey, intermediates ...*x
return
}

/*
ParseLeafCertSimple is like ParseLeafCert, but *only* returns leaf certificates;
no key correlation or chain building/association occurs.

TODO: Currently not fully implemented.
*/
/*
func ParseLeafCertSimple() () {

// TODO

return
}
*/

/*
ParsePemBundle splits a combined PEM (also referred to as "bundled PEMs") into one or more TlsPkiChains.

(combinedRaw must be the PEM-encoded bytes, not the decoded contained bytes.)

TODO: Currently not fully implemented.
*/
/*
func ParsePemBundle(combinedRaw []byte) (chains []*TlsPkiChain, err error) {

var roots []*x509.Certificate
var inters []*x509.Certificate
var keys []crypto.PrivateKey
var certs []tls.Certificate

if _, roots, inters, err = ParseCA(combinedRaw); err != nil {
return
}
if keys, err = ParsePrivateKey(combinedRaw); err != nil {
return
}

// TODO

return
}
*/

/*
ParsePrivateKey parses PEM bytes to a private key. Multiple keys may be concatenated in the same file.

@ -807,20 +878,36 @@ func ParsePrivateKey(keyRaw []byte) (keys []crypto.PrivateKey, err error) {
}
}

// TODO
return
}

// SplitPem splits a single block of bytes into one (or more) (encoding/)pem.Blocks. Currently err is not used, but is reserved for future use.
func SplitPem(pemRaw []byte) (blocks []*pem.Block, err error) {

var pemBlocks *PemBlocks

if pemBlocks, err = SplitPemBlocks(pemRaw); err != nil {
return
}

blocks = pemBlocks.Split()

return
}

// SplitPem splits a single block of bytes into one (or more) (encoding/)pem.Blocks.
func SplitPem(pemRaw []byte) (blocks []*pem.Block, err error) {
// SplitPemBlocks splits a single block of bytes into a PemBlocks. Currently err is not used, but is reserved for future use.
func SplitPemBlocks(pemRaw []byte) (blocks *PemBlocks, err error) {

var block *pem.Block
var nativeBlocks []*pem.Block
var rest []byte

for block, rest = pem.Decode(pemRaw); block != nil; block, rest = pem.Decode(rest) {
blocks = append(blocks, block)
nativeBlocks = append(nativeBlocks, block)
}

blocks = new(PemBlocks)
*blocks = nativeBlocks

return
}

View File

@ -0,0 +1,89 @@
package cryptparse

import (
`bytes`
`encoding/pem`
)

// Bytes returns a combined PEM bytes of all blocks in a PemBlocks. Any nil, empty, or otherwise invalid blocks are skipped.
func (p *PemBlocks) Bytes() (combined []byte) {

var err error
var buf *bytes.Buffer = new(bytes.Buffer)

for _, block := range p.Split() {
if block == nil || block.Bytes == nil || block.Type == "" {
continue
}
// We've ruled out "contextual" errors, so we ignore it here.
if err = pem.Encode(buf, block); err != nil {
continue
}
}

combined = buf.Bytes()
_ = err

return
}

// BytesStrict is like Bytes but is much more strict/safe (invalid/empty/nil blocks are not skipped) and will return any errors on encoding.
func (p *PemBlocks) BytesStrict() (combined []byte, err error) {

var buf *bytes.Buffer = new(bytes.Buffer)

for _, block := range p.Split() {
if err = pem.Encode(buf, block); err != nil {
return
}
}

combined = buf.Bytes()

return
}

// BytesSplit returns separate PEM bytes of each block in a PemBlocks. Any nil, empty, or otherwise invalid blocks are skipped.
func (p *PemBlocks) BytesSplit() (pems [][]byte) {

var b []byte

for _, block := range p.Split() {
if block == nil || block.Bytes == nil || block.Type == "" {
continue
}
// We've ruled out "contextual" errors, so we ignore it here.
b = pem.EncodeToMemory(block)
pems = append(pems, b)
}

return
}

// BytesSplitStrict is like BytesSplit but is much more strict/safe (invalid/empty/nil blocks are not skipped) and will return any errors on encoding.
func (p *PemBlocks) BytesSplitStrict() (pems [][]byte, err error) {

var buf *bytes.Buffer = new(bytes.Buffer)

for _, block := range p.Split() {
buf.Reset()
if err = pem.Encode(buf, block); err != nil {
return
}
pems = append(pems, buf.Bytes())
}

return
}

// Split returns a more primitive-friendly representation of a PemBlocks.
func (p *PemBlocks) Split() (native []*pem.Block) {

if p == nil {
return
}

native = *p

return
}

View File

@ -1,30 +1,103 @@
package cryptparse

import (
`crypto`
`crypto/tls`
`crypto/x509`
`encoding/pem`
`encoding/xml`
`net/url`

`github.com/Luzifer/go-dhparam`
)

// PemBlocks is a combined set of multiple pem.Blocks.
type PemBlocks []*pem.Block

// TlsFlat provides an easy structure to marshal/unmarshal a tls.Config from/to a data structure (JSON, XML, etc.).
type TlsFlat struct {
XMLName xml.Name `xml:"tlsConfig" json:"-" yaml:"-" toml:"-"`
SniName string `json:"sni_name" xml:"sniName,attr" yaml:"SniName" toml:"SniName" required:"true" validate:"required"`
SkipVerify bool `json:"skip_verify,omitempty" xml:"skipVerify,attr,omitempty" yaml:"SkipVerify,omitempty" toml:"SkipVerify,omitempty"`
Certs []*TlsFlatCert `json:"certs,omitempty" xml:"certs>cert,omitempty" yaml:"Certs,omitempty" toml:"Certs,omitempty" validate:"omitempty,dive"`
CaFiles []string `json:"ca_files,omitempty" xml:"roots>ca,omitempty" yaml:"CaFiles,omitempty" toml:"CaFiles,omitempty" validate:"omitempty,dive,filepath"`
CipherSuites []string `json:"cipher_suites,omitempty" xml:"ciphers,omitempty" yaml:"CipherSuites,omitempty" toml:"CipherSuites,omitempty"`
MinTlsProtocol *string `json:"min_tls_protocol,omitempty" xml:"minTlsProtocol,attr,omitempty" yaml:"MinTlsProtocol,omitempty" toml:"MinTlsProtocol,omitempty"`
MaxTlsProtocol *string `json:"max_tls_protocol,omitempty" xml:"maxTlsProtocol,attr,omitempty" yaml:"MaxTlsProtocol,omitempty" toml:"MaxTlsProtocol,omitempty"`
// SniName represents the expected Server Name Indicator's name. See TlsUriParamSni.
SniName string `json:"sni_name" toml:"SNIName" yaml:"SNI Name" xml:"sniName,attr" required:"true" validate:"required"`
// SkipVerify, if true, will bypass certificate verification. You generally should not enable this. See TlsUriParamNoVerify.
SkipVerify bool `json:"skip_verify,omitempty" toml:"SkipVerify,omitempty" yaml:"Skip Verification,omitempty" xml:"skipVerify,attr,omitempty"`
// Certs contains 0 or more TlsFlatCert certificate definitions. See TlsUriParamCert and TlsUriParamKey as well.
Certs []*TlsFlatCert `json:"certs,omitempty" toml:"Certs,omitempty" yaml:"Certificates,omitempty" xml:"certs>cert,omitempty"validate:"omitempty,dive"`
// CaFiles contains filepaths to CA certificates/"trust anchors" in PEM format. They may be combined. See TlsUriParamCa.
CaFiles []string `json:"ca_files,omitempty" toml:"CaFiles,omitempty" yaml:"CA Files,omitempty" xml:"roots>ca,omitempty" validate:"omitempty,dive,filepath"`
// CipherSuites represents desired ciphers/cipher suites for this TLS environment. See TlsUriParamCipher.
CipherSuites []string `json:"cipher_suites,omitempty" toml:"CipherSuites,omitempty" yaml:"Cipher Suites,omitempty" xml:"ciphers,omitempty"`
// Curves specifies desired cryptographic curves to be used. See TlsUriParamCurve.
Curves []string `json:"curves,omitempty" xml:"curves>curve,omitempty" yaml:"Curves,omitempty" toml:"Curves,omitempty" validate:"omitempty,dive"`
// MinTlsProtocol specifies the minimum TLS version. See TlsUriParamMinTls.
MinTlsProtocol *string `json:"min_tls_protocol,omitempty" xml:"minTlsProtocol,attr,omitempty" yaml:"MinTlsProtocol,omitempty" toml:"MinTlsProtocol,omitempty"`
// MaxTlsProtocol specifies the maximum TLS version. See TlsUriParamMaxTls.
MaxTlsProtocol *string `json:"max_tls_protocol,omitempty" xml:"maxTlsProtocol,attr,omitempty" yaml:"MaxTlsProtocol,omitempty" toml:"MaxTlsProtocol,omitempty"`
}

// TlsFlatCert represents a certificate (and, possibly, paired key).
type TlsFlatCert struct {
XMLName xml.Name `xml:"cert" json:"-" yaml:"-" toml:"-"`
// KeyFile is a filepath to a PEM-encoded key file. See TlsUriParamKey.
KeyFile *string `json:"key,omitempty" xml:"key,attr,omitempty" yaml:"Key,omitempty" toml:"Key,omitempty" validate:"omitempty,filepath"`
// CertFile is a filepath to a PEM-encoded certificate file. See TlsUriParamCert.
CertFile string `json:"cert" xml:",chardata" yaml:"Certificate" toml:"Certificate" required:"true" validate:"required,filepath"`
}

// TlsPkiChain contains a whole X.509 PKI chain -- Root CA(s) (trust anchors) which sign Intermediate(s) which sign Certificate(s).
type TlsPkiChain struct {
/*
Roots are all trust anchors/root certificates.

Roots are certificates that are self-signed and can issue certificates/sign CSRs.
*/
Roots []*x509.Certificate
// RootsPool is an x509.CertPool representation of Roots.
RootsPool *x509.CertPool
/*
Intermediates are signers that should not be trusted directly, but instead included in the verification/validation chain.

Intermediates are certificates that are NOT self-signed (they should be signed by at least one Roots/RootsPool)
but CAN issue certificates/sign CSRs.
*/
Intermediates []*x509.Certificate
// IntermediatesPool is an x509.CertPool representation of Intermediates.
IntermediatesPool *x509.CertPool
/*
Certificates are "leaf certificates"; typically these are the certificates used directly by servers/users.

A certificate is considered a Certificate here if it is NOT self-signed and is NOT able to issue certificates/sign CSRs.
*/
Certificates []*tls.Certificate
// CertificatesPool is an x509.CertPool representation of Certificates.
CertificatesPool *x509.CertPool
/*
UnmatchedCerts contains Certificates that:
* Do not match any of Roots/RootsPool as its signer, and/or
* Do not match any Intermediates/IntermediatesPool as its signer, and/or
* Does not meet requirements for Roots/RootsPool, and/or
* Does not meet requirements for Intermediates/IntermediatesPool, and/or
* Has no matching crypto.PrivateKey found.

These should generally *never* be used if they were parsed in.
They represent "stray" certificates that have no logical chain/path found
and are likely unusable for purposes of this environment.
*/
UnmatchedCerts []*x509.Certificate
// UnmatchedCertsPool is an x509.CertPool representation of UnmatchedCerts.
UnmatchedCertsPool *x509.CertPool
/*
UnmatchedKeys represent parsed private keys that have no matching corresponding certifificate.

These should generally *never* be used if they were parsed in.
They represent "stray" keys that have no logical chain/path found
and are likely unusable for purposes of this environment.
*/
UnmatchedKeys []crypto.PrivateKey
// DhParams represent any found DH parameters. This will usually be empty.
DhParams []*dhparam.DH
}

type TlsUri struct {
*url.URL
}

1
go.mod
View File

@ -3,6 +3,7 @@ module r00t2.io/sysutils
go 1.21

require (
github.com/Luzifer/go-dhparam v1.2.0
github.com/davecgh/go-spew v1.1.1
github.com/g0rbe/go-chattr v1.0.1
github.com/go-playground/validator/v10 v10.22.0

2
go.sum
View File

@ -1,3 +1,5 @@
github.com/Luzifer/go-dhparam v1.2.0 h1:YwDf15FTsVriTynCv1qF+1Inh6E8Dg1+28tPEA3pvFo=
github.com/Luzifer/go-dhparam v1.2.0/go.mod h1:hnazoxBTsXnRvGXAosio70Tb1lWowquyhVdvsXdlIPc=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=