1
0

reflection work so far...

This commit is contained in:
brent saner
2025-01-25 16:11:19 -05:00
parent bf887ce948
commit 1471dc29ed
31 changed files with 2240 additions and 150 deletions

234
funcs.go
View File

@@ -11,11 +11,14 @@ import (
`crypto/x509`
`encoding/pem`
`errors`
`io`
`net/url`
`os`
`path/filepath`
`strconv`
`strings`
`r00t2.io/sysutils/envs`
`r00t2.io/sysutils/paths`
)
@@ -85,6 +88,60 @@ func IsMatchedPair(privKey crypto.PrivateKey, cert *x509.Certificate) (isMatched
return
}
/*
ParseMtlsMode parses string s and attempts to derive a TLS client certificate
auth mode from it.
The string may either be the name (as per https://pkg.go.dev/crypto/tls#ClientAuthType)
or an int (normal, hex, etc. string representation).
*/
func ParseMtlsMode(s string) (mode tls.ClientAuthType, err error) {
var nm string
var n int64
var m tls.ClientAuthType
if n, err = strconv.ParseInt(s, 10, 64); err != nil {
if errors.Is(err, strconv.ErrSyntax) {
// It's a name; parse below.
err = nil
} else {
return
}
} else {
// It's a number.
m = tls.ClientAuthType(n)
if !strings.HasPrefix(m.String(), "ClientAuthType(") {
// It's valid; send it.
mode = m
return
} else {
// It's invalid.
err = ErrBadMtlsMode
return
}
}
// It's a name. First normalize the string so we don't need to do so many transforms.
nm = strings.ToLower(strings.TrimSpace(s))
// Then keep going until we either find it or we run out of valid auth types.
for i := 0; ; i++ {
m = tls.ClientAuthType(i)
if strings.ToLower(m.String()) == nm {
// Found; send it.
mode = m
break
}
if strings.HasPrefix(m.String(), "ClientAuthType(") {
// We've reached the end of valid auth names and it still wasn't found.
err = ErrBadMtlsMode
return
}
}
return
}
/*
ParseTlsCipher parses string s and attempts to derive a TLS cipher suite (as a uint16) from it.
Use ParseTlsCipherSuite if you wish for a tls.CipherSuite instead.
@@ -403,34 +460,42 @@ func ParseTlsUri(tlsUri *url.URL) (tlsConf *tls.Config, err error) {
var b []byte
var rootCAs *x509.CertPool
var mtlsCAs *x509.CertPool
var intermediateCAs []*x509.Certificate
var concatCAs []*x509.Certificate
var privKeys []crypto.PrivateKey
var tlsCerts []tls.Certificate
var allowInvalid bool
var ciphers []uint16
var curves []tls.CurveID
var params map[string][]string
var ok bool
var val string
var minVer uint16
var maxVer uint16
var ignoreMissing bool
var keylog io.Writer
var clientAuthType tls.ClientAuthType
var params map[tlsUriParam][]string = make(map[tlsUriParam][]string)
var buf *bytes.Buffer = new(bytes.Buffer)
var srvNm string = tlsUri.Hostname()
params = tlsUri.Query()
if params == nil {
if tlsUri.Query() == nil || len(tlsUri.Query()) == 0 {
tlsConf = &tls.Config{
ServerName: srvNm,
}
return
}
for k, v := range tlsUri.Query() {
params[tlsUriParam(k)] = v
}
// These are all filepath(s).
for _, k := range []string{
TlsUriParamCa,
TlsUriParamCert,
TlsUriParamKey,
for _, k := range []tlsUriParam{
ParamCa,
ParamCert,
ParamKey,
ParamMtlsCa,
} {
if _, ok = params[k]; ok {
for idx, _ := range params[k] {
@@ -441,15 +506,59 @@ func ParseTlsUri(tlsUri *url.URL) (tlsConf *tls.Config, err error) {
}
}
if _, ok = params[ParamIgnoreMissing]; ok {
val = strings.ToLower(params[ParamIgnoreMissing][0])
for _, i := range paramBoolValsTrue {
if i == val {
ignoreMissing = true
break
}
}
}
// This *might* be a filepath.
if _, ok = params[ParamKeylog]; ok {
switch params[ParamKeylog][0] {
case KeyLogBufVal:
keylog = new(bytes.Buffer)
case KeyLogEnvVal:
val = params[ParamKeylog][0]
if envs.HasEnv(val) {
val = os.Getenv(val)
if err = paths.RealPath(&val); err != nil {
return
}
if err = os.MkdirAll(filepath.Dir(val), 0700); err != nil {
return
}
if keylog, err = os.OpenFile(val, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o0600); err != nil {
return
}
}
default:
if err = paths.RealPath(&params[ParamKeylog][0]); err != nil {
return
}
if err = os.MkdirAll(filepath.Dir(params[ParamKeylog][0]), 0700); err != nil {
return
}
if keylog, err = os.OpenFile(params[ParamKeylog][0], os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o0600); err != nil {
return
}
}
}
// CA cert(s).
buf.Reset()
if _, ok = params[TlsUriParamCa]; ok {
if _, ok = params[ParamCa]; ok {
rootCAs = x509.NewCertPool()
for _, c := range params[TlsUriParamCa] {
for _, c := range params[ParamCa] {
if b, err = os.ReadFile(c); err != nil {
if errors.Is(err, os.ErrNotExist) {
if errors.Is(err, os.ErrNotExist) && ignoreMissing {
err = nil
continue
} else {
return
}
}
buf.Write(b)
@@ -463,12 +572,12 @@ func ParseTlsUri(tlsUri *url.URL) (tlsConf *tls.Config, err error) {
}
}
// Keys. These are done first so we can match to a client certificate.
// Keys. These are done first so we can match to a leaf certificate.
buf.Reset()
if _, ok = params[TlsUriParamKey]; ok {
for _, k := range params[TlsUriParamKey] {
if _, ok = params[ParamKey]; ok {
for _, k := range params[ParamKey] {
if b, err = os.ReadFile(k); err != nil {
if errors.Is(err, os.ErrNotExist) {
if errors.Is(err, os.ErrNotExist) && ignoreMissing {
err = nil
continue
} else {
@@ -482,12 +591,12 @@ func ParseTlsUri(tlsUri *url.URL) (tlsConf *tls.Config, err error) {
}
}
// (Client) Certificate(s).
// (Leaf) Certificate(s).
buf.Reset()
if _, ok = params[TlsUriParamCert]; ok {
for _, c := range params[TlsUriParamCert] {
if _, ok = params[ParamCert]; ok {
for _, c := range params[ParamCert] {
if b, err = os.ReadFile(c); err != nil {
if errors.Is(err, os.ErrNotExist) {
if errors.Is(err, os.ErrNotExist) && ignoreMissing {
err = nil
continue
} else {
@@ -496,19 +605,24 @@ func ParseTlsUri(tlsUri *url.URL) (tlsConf *tls.Config, err error) {
}
buf.Write(b)
}
if tlsCerts, err = ParseLeafCert(buf.Bytes(), privKeys, intermediateCAs...); err != nil {
if tlsCerts, concatCAs, err = ParseLeafCert(buf.Bytes(), privKeys, intermediateCAs...); err != nil {
return
}
if concatCAs != nil {
for _, ca := range concatCAs {
rootCAs.AddCert(ca)
}
}
}
// Hostname (Override).
if _, ok = params[TlsUriParamSni]; ok {
srvNm = params[TlsUriParamSni][0]
if _, ok = params[ParamSni]; ok {
srvNm = params[ParamSni][0]
}
// Disable Verification.
if _, ok = params[TlsUriParamNoVerify]; ok {
val = strings.ToLower(params[TlsUriParamNoVerify][0])
if _, ok = params[ParamNoVerify]; ok {
val = strings.ToLower(params[ParamNoVerify][0])
for _, i := range paramBoolValsTrue {
if i == val {
allowInvalid = true
@@ -517,39 +631,73 @@ func ParseTlsUri(tlsUri *url.URL) (tlsConf *tls.Config, err error) {
}
}
// Client/mTLS cert auth mode.
if _, ok = params[ParamMtlsMode]; ok {
val = params[ParamMtlsMode][0]
if clientAuthType, err = ParseMtlsMode(val); err != nil {
return
}
}
// Client/mTLS roots.
buf.Reset()
if clientAuthType != tls.NoClientCert {
if _, ok = params[ParamMtlsCa]; ok {
mtlsCAs = x509.NewCertPool()
for _, c := range params[ParamMtlsCa] {
if b, err = os.ReadFile(c); err != nil {
if errors.Is(err, os.ErrNotExist) && ignoreMissing {
err = nil
continue
} else {
return
}
}
buf.Write(b)
}
if mtlsCAs, _, _, err = ParseCA(buf.Bytes()); err != nil {
return
}
} else {
mtlsCAs = rootCAs.Clone()
}
}
// Ciphers.
if _, ok = params[TlsUriParamCipher]; ok {
ciphers = ParseTlsCiphers(strings.Join(params[TlsUriParamCipher], ","))
if _, ok = params[ParamCipher]; ok {
ciphers = ParseTlsCiphers(strings.Join(params[ParamCipher], ","))
}
// Minimum TLS Protocol Version.
if _, ok = params[TlsUriParamMinTls]; ok {
if minVer, err = ParseTlsVersion(params[TlsUriParamMinTls][0]); err != nil {
if _, ok = params[ParamMinTls]; ok {
if minVer, err = ParseTlsVersion(params[ParamMinTls][0]); err != nil {
return
}
}
// Maximum TLS Protocol Version.
if _, ok = params[TlsUriParamMaxTls]; ok {
if maxVer, err = ParseTlsVersion(params[TlsUriParamMaxTls][0]); err != nil {
if _, ok = params[ParamMaxTls]; ok {
if maxVer, err = ParseTlsVersion(params[ParamMaxTls][0]); err != nil {
return
}
}
// Curves.
if _, ok = params[TlsUriParamCurve]; ok {
curves = ParseTlsCurves(strings.Join(params[TlsUriParamCurve], ","))
if _, ok = params[ParamCurve]; ok {
curves = ParseTlsCurves(strings.Join(params[ParamCurve], ","))
}
tlsConf = &tls.Config{
Certificates: tlsCerts,
RootCAs: rootCAs,
ServerName: srvNm,
ClientAuth: clientAuthType,
ClientCAs: mtlsCAs,
InsecureSkipVerify: allowInvalid,
CipherSuites: ciphers,
MinVersion: minVer,
MaxVersion: maxVer,
CurvePreferences: curves,
KeyLogWriter: keylog,
}
return
@@ -694,36 +842,36 @@ func ParseDhParams(dhRaw []byte) (params []*dhparam.DH, err error) {
*/
/*
ParseLeafCert parses PEM bytes from a (client) certificate file, iterates over a slice of
ParseLeafCert parses PEM bytes from a certificate file, iterates over a slice of
crypto.PrivateKey (finding one that matches), and returns one (or more) tls.Certificate.
The key may also be combined with the certificate in the same file.
If no private key matches or no client cert is found in the file, tlsCerts will be nil/missing
If no private key matches or no leaf cert is found in the file, tlsCerts will be nil/missing
that certificate but no error will be returned.
This behavior can be avoided by passing a nil slice to keys.
Any leaf certificates ("server" certificate, as opposed to a signer/issuer) found in the file
Any leaf certificates ("server"/"client" certificate, as opposed to a signer/issuer) found in the file
will be assumed to be the desired one(s).
Any additional/supplementary intermediates may be provided. Any present in the PEM bytes (certRaw) will be included.
Any additional/supplementary intermediates may be provided. Any present in the PEM bytes (certRaw) will be included
in the tls.Certificate.Certificates.
Any *root* CAs found will be discarded. They should/can be extracted seperately via ParseCA.
Any *root* CAs found will be split out separately into caCerts and NOT included.
They should/can be extracted seperately via ParseCA.
The parsed and paired certificates and keys can be found in each respective tls.Certificate.Leaf and tls.Certificate.PrivateKey.
The parsed and paired certificates and keys can be found in each respective tls.Certificate[n].Leaf and tls.Certificate[n].PrivateKey.
Any certs without a corresponding key will be discarded.
*/
func ParseLeafCert(certRaw []byte, keys []crypto.PrivateKey, intermediates ...*x509.Certificate) (tlsCerts []tls.Certificate, err error) {
func ParseLeafCert(certRaw []byte, keys []crypto.PrivateKey, intermediates ...*x509.Certificate) (tlsCerts []tls.Certificate, caCerts []*x509.Certificate, err error) {
var pemBlocks []*pem.Block
var cert *x509.Certificate
var certs []*x509.Certificate
var caCerts []*x509.Certificate
var parsedKeys []crypto.PrivateKey
var isMatched bool
var foundKey crypto.PrivateKey
var interBytes [][]byte
var skipKeyPair bool = keys == nil
var parsedKeysBuf *bytes.Buffer = new(bytes.Buffer)
if pemBlocks, err = SplitPem(certRaw); err != nil {
@@ -777,7 +925,7 @@ func ParseLeafCert(certRaw []byte, keys []crypto.PrivateKey, intermediates ...*x
break
}
}
if foundKey == nil && !skipKeyPair {
if foundKey == nil {
continue
}
tlsCerts = append(
@@ -790,8 +938,6 @@ func ParseLeafCert(certRaw []byte, keys []crypto.PrivateKey, intermediates ...*x
)
}
_ = caCerts
return
}