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
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
31 changed files with 2240 additions and 150 deletions

View File

@ -0,0 +1,4 @@
#!/bin/bash

go generate
git add consts_param_map.go

8
.gitignore vendored
View File

@ -26,6 +26,14 @@
*.so *.so
*.dylib *.dylib


# The test data is intended to be generated locally.
# There's no reason to pollute the repo with it.
_extra/gen_test_pki/_testdata/*
!_extra/gen_test_pki/_testdata/.keep
_extra/gen_test_pki/gen_test_pki
_testdata/*
!_testdata/.keep

# Test binary, built with `go test -c` # Test binary, built with `go test -c`
*.test *.test



View File

View File

@ -0,0 +1,144 @@
package main

import (
`crypto/x509`
`crypto/x509/pkix`
`embed`
`net`
`time`
)

var (
pairTypes []string = []string{
"ca",
"inter",
"leaf_server",
"leaf_user",
}

keyTypes []string = []string{
/*
Per:
https://pkg.go.dev/crypto/x509#CreateCertificate
https://pkg.go.dev/crypto/x509#CreateCertificateRequest
ECDH keys are not supported for certificates (only ECDSA, ED25519, and RSA).
*/
// "ecdh",
"ecdsa",
"ed25519",
"rsa",
}

// Populated by init.
pairs map[string]*Pair = make(map[string]*Pair)
)

var (
//go:embed "_testdata/*"
pems embed.FS
)

const (
caCn string = "gen_test_pki Root CA"
interCn string = "gen_test_pki Intermediate CA"
serverCn string = "server.example.com"
userCn string = "username@example.com"
)

var (
pkixCommon *pkix.Name = &pkix.Name{
Country: []string{
"XX",
},
Organization: []string{
"An Example Organization",
},
OrganizationalUnit: []string{
"An Example Department",
},
Locality: []string{
"Some City",
},
Province: []string{
"Some State",
},
StreetAddress: []string{
"123 Example Street",
},
PostalCode: []string{
"12345",
},
// SerialNumber: "", // SerialNumber should be blank, and contextually generated via getSerial().
// CommonName: "", // CommonName should be blank, and contextually generated via getSubj().
Names: nil,
ExtraNames: nil,
}
certTpl map[string]*x509.Certificate = map[string]*x509.Certificate{
"ca": &x509.Certificate{
SerialNumber: getSerial(),
Subject: getSubj(caCn),
NotBefore: time.Now().Add(time.Second * -10),
NotAfter: time.Now().Add(10 * 365 * 24 * time.Hour), // (about) 10 years
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 1,
},
"inter": &x509.Certificate{
SerialNumber: getSerial(),
Subject: getSubj(interCn),
NotBefore: time.Now().Add(time.Second * -9),
NotAfter: time.Now().Add(9 * 365 * 24 * time.Hour), // (about) 9 years
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 0,
},
"leaf_server": &x509.Certificate{
SerialNumber: getSerial(),
Subject: getSubj(serverCn),
NotBefore: time.Now().Add(time.Second * -8),
NotAfter: time.Now().Add(9 * 365 * 24 * time.Hour), // (about) 8 years
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
},
},
"leaf_user": &x509.Certificate{
SerialNumber: getSerial(),
Subject: getSubj(userCn),
NotBefore: time.Now().Add(time.Second * -8),
NotAfter: time.Now().Add(9 * 365 * 24 * time.Hour), // (about) 8 years
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
},
},
}
csrs map[string]*x509.CertificateRequest = map[string]*x509.CertificateRequest{
"inter": &x509.CertificateRequest{
Subject: getSubj(interCn),
},
"leaf_server": &x509.CertificateRequest{
Subject: getSubj(serverCn),
IPAddresses: []net.IP{
net.IP(net.ParseIP("127.0.0.1")),
net.IP(net.ParseIP("::ffff:127.0.0.1")),
net.IP(net.ParseIP("::1")),
},
},
"leaf_user": &x509.CertificateRequest{
Subject: getSubj(userCn),
},
}
parents map[string]string = map[string]string{
"inter": "ca",
"leaf_server": "inter",
"leaf_user": "inter",
}
certgenOrder []string = []string{
"inter",
"leaf_server",
"leaf_user",
}
)

View File

@ -0,0 +1,476 @@
package main

import (
`bytes`
`crypto`
`crypto/ecdh`
`crypto/ecdsa`
`crypto/ed25519`
`crypto/elliptic`
`crypto/rand`
`crypto/rsa`
`crypto/x509`
`crypto/x509/pkix`
`encoding/binary`
`encoding/pem`
`errors`
`fmt`
`log`
`math/big`
`os`
`path/filepath`

`github.com/brunoga/deep`
`github.com/google/uuid`
)

/*
getKeyFpath returns a (relative) path for a key PEM file (PKCS#8).

This is used both when fetching from embed.FS and when generating new keys.
*/
func getKeyFpath(pairType, keyType string) (path string) {

path = filepath.Join("_testdata", fmt.Sprintf("%s_%s_key.pem", pairType, keyType))

return
}

/*
getCertFpath returns a (relative) path for a cert PEM file.

This is used both when fetching from embed.FS and when generating new certs.
*/
func getCertFpath(pairType, keyType string) (path string) {

path = filepath.Join("_testdata", fmt.Sprintf("%s_%s_cert.pem", pairType, keyType))

return
}

/*
getChainFpath returns a (relative) path for a chained cert PEM file.

This is used both when fetching from embed.FS and when generating new certs.
*/
func getChainFpath(pairType, keyType string) (path string) {

path = filepath.Join("_testdata", fmt.Sprintf("%s_%s_cert_chained.pem", pairType, keyType))

return
}

/*
getCsrFpath returns a (relative) path for a CSR PEM file.

This is used both when fetching from embed.FS and when generating new CSRs.
*/
func getCsrFpath(pairType, keyType string) (path string) {

path = filepath.Join("_testdata", fmt.Sprintf("%s_%s_csr.pem", pairType, keyType))

return
}

/*
getKeypair takes cert type t and key type kt and returns the crypto.Private and crypto.Public keys for it.

It assumes that loadKeys() at the *least* has already been called.
*/
func getKeypair(t, kt string) (priv crypto.PrivateKey, pub crypto.PublicKey) {

priv = pairs[t].privKeys[kt]

switch k := priv.(type) {
case *ecdh.PrivateKey:
pub = k.Public()
case *ecdsa.PrivateKey:
pub = k.Public()
case ed25519.PrivateKey: // This is correct. Unlike other kt's, ed25519 doesn't use pointers.
pub = k.Public()
case *rsa.PrivateKey:
pub = k.Public()
}

return
}

/*
getSerial returns a (pseudo-)random certificate based on a UUIDv4 (RFC 4122 type 4).

This guarantees not only that renewals (if issued/implemented) are reasonably guaranteed
to be different from the past issuance but also that the serial issuance is non-sequential
(both are common modern requirements of modern browser-trusted CAs; see
https://cabforum.org/working-groups/server/baseline-requirements/documents/)
*/
func getSerial() (serial *big.Int) {

var b []byte
var n int64
var u uuid.UUID = uuid.New()

b = u[:]

n = int64(binary.BigEndian.Uint64(b))
// Serials must be positive.
if n < 0 {
n = -n
}

serial = big.NewInt(n)

return
}

// getTpl returns a version of certificate template tpl with a randomized serial.
func getTpl(tpl *x509.Certificate) (newTpl *x509.Certificate) {

newTpl = new(x509.Certificate)
*newTpl = *tpl

newTpl.SerialNumber = getSerial()

return
}

// getSubj returns a cert/CSR-specific pkix.Name from a given cn (commonName).
func getSubj(cn string) (newSubj pkix.Name) {

newSubj = deep.MustCopy(*pkixCommon)
newSubj.CommonName = cn

return
}

// loadKeys either loads from pems or generates and writes out the PEM keys.
func loadKeys() (err error) {

var b []byte
var t string
var kt string
var ok bool
var pemBlock *pem.Block
var keybuf *bytes.Buffer = new(bytes.Buffer)

// Load in any existing keys.
for _, t = range pairTypes {
for _, kt = range keyTypes {
log.Printf("Loading %s key %s\n", t, kt)
if b, err = pems.ReadFile(getKeyFpath(t, kt)); err != nil {
if errors.Is(err, os.ErrNotExist) {
// Will generate missing below
pairs[t] = &Pair{
pairType: t,
keyBytes: make(map[string][]byte),
privKeys: make(map[string]crypto.PrivateKey),
certBytes: make(map[string][]byte),
certs: make(map[string]*x509.Certificate),
csrBytes: make(map[string][]byte),
csrs: make(map[string]*x509.CertificateRequest),
chainParentBytes: make(map[string][]byte),
chainParent: make(map[string]*x509.Certificate),
}
err = nil
continue
}
return
}
if _, ok = pairs[t]; !ok {
pairs[t] = &Pair{
pairType: t,
keyBytes: make(map[string][]byte),
privKeys: make(map[string]crypto.PrivateKey),
certBytes: make(map[string][]byte),
certs: make(map[string]*x509.Certificate),
csrBytes: make(map[string][]byte),
csrs: make(map[string]*x509.CertificateRequest),
}
}
pairs[t].keyBytes[kt] = b
if pairs[t].privKeys[kt], err = x509.ParsePKCS8PrivateKey(b); err != nil {
return
}
}
}
// Generate any missing keys.
for _, t = range pairTypes {
for _, kt = range keyTypes {
if _, ok = pairs[t].privKeys[kt]; !ok {
log.Printf("Generating %s key %s\n", t, kt)
keybuf.Reset()
switch kt {
case "ecdh":
if pairs[t].privKeys[kt], err = ecdh.X25519().GenerateKey(rand.Reader); err != nil {
return
}
case "ecdsa":
if pairs[t].privKeys[kt], err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader); err != nil {
return
}
case "ed25519":
if _, pairs[t].privKeys[kt], err = ed25519.GenerateKey(rand.Reader); err != nil {
return
}
case "rsa":
if pairs[t].privKeys[kt], err = rsa.GenerateKey(rand.Reader, 4096); err != nil {
return
}
}
if b, err = x509.MarshalPKCS8PrivateKey(pairs[t].privKeys[kt]); err != nil {
log.Panicln(err)
}
pemBlock = &pem.Block{
Type: "PRIVATE KEY",
Headers: nil,
Bytes: b,
}
b = pem.EncodeToMemory(pemBlock)
pairs[t].keyBytes[kt] = b
if err = os.WriteFile(getKeyFpath(t, kt), b, 0o0600); err != nil {
return
}
}
}
}

return
}

// loadCerts combines all loadCert* functions in the proper order. It is expected that loadKeys has already been run.
func loadCerts() (err error) {

var b []byte
var t string
var kt string
var tkt [2]string
var chainMissing [][2]string = make([][2]string, 0, (len(certgenOrder)-1)*len(keyTypes))

if err = loadCertCa(); err != nil {
return
}
if err = loadCertIssued(); err != nil {
return
}

// And create chained certs of leaves so they fully validate.
for _, t = range certgenOrder {
if t == "inter" {
continue // Don't bother with chaining the intermediate. If we play around with multiple intermediates, we will.
}
for _, kt = range keyTypes {
pairs[t].chainParent[kt] = pairs[parents[t]].certs[kt]
log.Printf("Loading %s chain %s\n", t, kt)
if b, err = pems.ReadFile(getChainFpath(t, kt)); err != nil {
if errors.Is(err, os.ErrNotExist) {
err = nil
chainMissing = append(chainMissing, [2]string{t, kt})
continue
}
return
}
// Found
pairs[t].chainParentBytes[kt] = b
}
}

// "Generate" missing.
for _, tkt = range chainMissing {
t = tkt[0]
kt = tkt[1]
log.Printf("Building %s chain %s\n", t, kt)
b = append(pairs[t].certBytes[kt], pairs[parents[t]].certBytes[kt]...)
if err = os.WriteFile(getChainFpath(t, kt), b, 0o0600); err != nil {
return
}
}

return
}

// loadCertCa loads (or generates) the root CA/anchor. It is expected that loadKeys has already been run.
func loadCertCa() (err error) {

var b []byte
var kt string
var ok bool
var privKey crypto.PrivateKey
var pubKey crypto.PublicKey
var pemBlock *pem.Block
var ktTpl *x509.Certificate

for _, kt = range keyTypes {
log.Printf("Loading CA certificate %s\n", kt)
if b, err = pems.ReadFile(getCertFpath("ca", kt)); err != nil {
if errors.Is(err, os.ErrNotExist) {
// Will generate missing below.
err = nil
continue
}
return
}
// Assume the mapped Pair exists per loadKeys.
pairs["ca"].certBytes[kt] = b
pemBlock, _ = pem.Decode(b)
if pairs["ca"].certs[kt], err = x509.ParseCertificate(pemBlock.Bytes); err != nil {
return
}
}

// Generate missing CA certs.
for _, kt = range keyTypes {
log.Printf("Generating CA certificate %s\n", kt)
if _, ok = pairs["ca"].certs[kt]; !ok {
ktTpl = getTpl(certTpl["ca"])
privKey, pubKey = getKeypair("ca", kt)
// Specifying the same cert template for both the template and parent params creates a self-signed.
if b, err = x509.CreateCertificate(
rand.Reader,
ktTpl,
ktTpl,
pubKey,
privKey,
); err != nil {
return
}
if pairs["ca"].certs[kt], err = x509.ParseCertificate(b); err != nil {
return
}
pemBlock = &pem.Block{
Type: "CERTIFICATE",
Headers: nil,
Bytes: b,
}
b = pem.EncodeToMemory(pemBlock)
pairs["ca"].certBytes[kt] = b
if err = os.WriteFile(getCertFpath("ca", kt), b, 0o0600); err != nil {
return
}
}
}

return
}

// loadCertIssued handles the intermediate, "server" leaf, and "user" leaf.
func loadCertIssued() (err error) {

var b []byte
var ok bool
var t string
var kt string
var tkt [2]string
var ktMap map[string]bool
var caCert *x509.Certificate
var caPrivKey crypto.PrivateKey
var certPrivKey crypto.PrivateKey
var certPubKey crypto.PublicKey
var pemBlock *pem.Block
var ktTpl *x509.Certificate
// map[<t>][<kt>]; map so we can condense dupes
var certMissing map[string]map[string]bool = make(map[string]map[string]bool)
var csrMissing [][2]string = make([][2]string, 0, len(certgenOrder)*len(keyTypes))

// CSRS
// Find existing CSRs and certs
for _, t = range certgenOrder {
for _, kt = range keyTypes {
log.Printf("Loading %s CSR %s\n", t, kt)
if b, err = pems.ReadFile(getCsrFpath(t, kt)); err != nil {
if errors.Is(err, os.ErrNotExist) {
err = nil
csrMissing = append(csrMissing, [2]string{t, kt})
continue
}
return
}
// Assume the mapped Pair exists per loadKeys.
pairs[t].csrBytes[kt] = b
pemBlock, _ = pem.Decode(b)
if pairs[t].csrs[kt], err = x509.ParseCertificateRequest(pemBlock.Bytes); err != nil {
return
}
log.Printf("Loading %s certificate %s\n", t, kt)
if b, err = pems.ReadFile(getCertFpath(t, kt)); err != nil {
if errors.Is(err, os.ErrNotExist) {
err = nil
if _, ok = certMissing[t]; !ok {
certMissing[t] = make(map[string]bool)
}
certMissing[t][kt] = true
continue
}
}
pairs[t].certBytes[kt] = b
pemBlock, _ = pem.Decode(b)
if pairs[t].certs[kt], err = x509.ParseCertificate(pemBlock.Bytes); err != nil {
return
}
}
}

// Generate missing CSRs.
for _, tkt = range csrMissing {
t = tkt[0]
kt = tkt[1]
log.Printf("Generating %s CSR %s\n", t, kt)
certPrivKey, certPubKey = getKeypair(t, kt)
if b, err = x509.CreateCertificateRequest(rand.Reader, csrs[t], certPrivKey); err != nil {
return
}
if pairs[t].csrs[kt], err = x509.ParseCertificateRequest(b); err != nil {
return
}
pemBlock = &pem.Block{
Type: "CERTIFICATE REQUEST",
Headers: nil,
Bytes: b,
}
b = pem.EncodeToMemory(pemBlock)
pairs[t].csrBytes[kt] = b
if err = os.WriteFile(getCsrFpath(t, kt), b, 0o0600); err != nil {
return
}
if _, ok = certMissing[t]; !ok {
certMissing[t] = make(map[string]bool)
}
certMissing[t][kt] = true
}

// Force re-gen of certs for above new CSRs and gen missing.
for _, t = range certgenOrder {
if ktMap, ok = certMissing[t]; !ok {
continue
}
for kt, _ = range ktMap {
log.Printf("Generating %s certificate %s\n", t, kt)
caCert = pairs[parents[t]].certs[kt]
caPrivKey = pairs[parents[t]].privKeys[kt]
_, certPubKey = getKeypair(t, kt)
ktTpl = getTpl(certTpl[t])
if b, err = x509.CreateCertificate(
rand.Reader,
ktTpl,
caCert,
certPubKey,
caPrivKey,
); err != nil {
return
}
if pairs[t].certs[kt], err = x509.ParseCertificate(b); err != nil {
return
}
pemBlock = &pem.Block{
Type: "CERTIFICATE",
Headers: nil,
Bytes: b,
}
b = pem.EncodeToMemory(pemBlock)
pairs[t].certBytes[kt] = b
if err = os.WriteFile(getCertFpath(t, kt), b, 0o0600); err != nil {
return
}
}
}

return
}

View File

@ -0,0 +1,9 @@
module r00t2.io/cryptparse/_extra/gen_test_pki

go 1.23.2

require (
github.com/brunoga/deep v1.2.4
github.com/davecgh/go-spew v1.1.1
github.com/google/uuid v1.6.0
)

View File

@ -0,0 +1,6 @@
github.com/brunoga/deep v1.2.4 h1:Aj9E9oUbE+ccbyh35VC/NHlzzjfIVU69BXu2mt2LmL8=
github.com/brunoga/deep v1.2.4/go.mod h1:GDV6dnXqn80ezsLSZ5Wlv1PdKAWAO4L5PnKYtv2dgaI=
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=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=

View File

@ -0,0 +1,21 @@
package main

import (
`log`
)

func init() {

var err error

// Keys need to be loaded/generated first for everything.
if err = loadKeys(); err != nil {
log.Panicln(err)
}

// Then the certificates.
if err = loadCerts(); err != nil {
log.Panicln(err)
}

}

View File

@ -0,0 +1,13 @@
package main

import (
`fmt`
)

func main() {

// Everything's handled in init()

fmt.Println("Done.")

}

View File

@ -0,0 +1,18 @@
package main

import (
`crypto`
`crypto/x509`
)

type Pair struct {
pairType string // see pairTypes
keyBytes map[string][]byte // see keyTypes for keys for this map
privKeys map[string]crypto.PrivateKey // see keyTypes for keys for this map
certBytes map[string][]byte // see keyTypes for keys for this map
certs map[string]*x509.Certificate // see keyTypes for keys for this map
csrs map[string]*x509.CertificateRequest // see keyTypes for keys for this map; does not exist for ca
csrBytes map[string][]byte // see keyTypes for keys for this map; does not exist for ca
chainParentBytes map[string][]byte
chainParent map[string]*x509.Certificate
}

0
_testdata/.keep Normal file
View File

208
consts.go
View File

@ -12,6 +12,10 @@ var (
tlsCurveNmToCurve map[string]tls.CurveID tlsCurveNmToCurve map[string]tls.CurveID
) )


const (
dfltStructTag string = "tlsUri"
)

const ( const (
MaxTlsCipher uint16 = tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 MaxTlsCipher uint16 = tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
MaxCurveId tls.CurveID = tls.X25519 // 29 MaxCurveId tls.CurveID = tls.X25519 // 29
@ -20,50 +24,56 @@ const (
DefaultNetType string = "tcp" DefaultNetType string = "tcp"
) )


// TlsUriParam* specifiy URL query parameters to parse a tls:// URI, and are used by TlsUri methods. const (
// KeyLogEnv specifies the TLS key log file.
// !! ONLY USE THIS FOR DEBUGGING !!
KeyLogEnv string = "SSLKEYLOGFILE"
/*
KeyLogEnvVal specifies the special ParamKeylog value to use the
value of the environment variable as named in KeyLogEnv.
*/
KeyLogEnvVal string = "_env_"
// KeyLogBufVal specifies the special ParamKeylog value to use an in-memory buffer.
KeyLogBufVal string = "_buf_"
)

//go:generate go run ./internal/constmap

/*
TlsUriParam* specifiy URL query parameters to parse a tls:// URI, and are used by TlsUri methods.

NOTE: If these consts' type or "Param*" prefix changes, internal/constmap/consts.go will also need to be changed.
The above go:generate creates (within this main module namespace):
tlsUriParamStrMap (a map of string(<tlsUriParam> const name) => <parameter>)
tlsUriStrParamMap (a map of <parameter> => string(<tlsUriParam> const name))
*/
const ( const (
/* /*
TlsUriParamCa specifies a path to a CA certificate PEM-encded DER file. ParamCa specifies a path to a CA certificate PEM-encded DER file.

If not specified, the system's roots/trust anchors are used.
Files specified here will be included *in addition to* any
embedded root anchors found in the ParamCert parameter's
value, if using concatenated cert chains.


It may be specified multiple times in a TLS URI. It may be specified multiple times in a TLS URI.
*/ */
TlsUriParamCa string = "pki_ca" ParamCa tlsUriParam = "pki_ca"
/* /*
TlsUriParamCert specifies a path to a client certificate PEM-encded DER file. ParamCert specifies a path to a leaf certificate PEM-encded DER file.

It may (and should/must) include any intermediate certificates necessary for
validation chain on the remote end.
It may include trust anchors, which will be considered *in addition to*
any ParamCa.
It may include a corresponding private key, which will be included for consideration
*in addition to* any ParamKey.


It may be specified multiple times in a TLS URI. It may be specified multiple times in a TLS URI.
*/ */
TlsUriParamCert string = "pki_cert" ParamCert tlsUriParam = "pki_cert"
/* /*
TlsUriParamKey specifies a path to a private key as a PEM-encded file. ParamCipher specifies one (or more) cipher(s)

It may be PKCS#1, PKCS#8, or PEM-encoded ASN.1 DER EC key.

Supported private key types are RSA, ED25519, ECDSA, and ECDH.

It may be specified multiple times in a TLS URI.
*/
TlsUriParamKey string = "pki_key"
/*
TlsUriParamNoVerify, if `1`, `yes`, `y`, or `true` indicate
that the TLS connection should not require verification of
the remote end (e.g. hostname matches, trusted chain, etc.).

Any other value for this parameter will be parsed as "False"
(meaning the remote end's certificate SHOULD be verified).

Only the first defined instance is parsed.
*/
TlsUriParamNoVerify string = "no_verify"
/*
TlsUriParamSni indicates that the TLS connection should expect this hostname
instead of the hostname specified in the URI itself.

Only the first defined instance is parsed.
*/
TlsUriParamSni string = "sni"
/*
TlsUriParamCipher specifies one (or more) cipher(s)
to specify for the TLS connection cipher negotiation. to specify for the TLS connection cipher negotiation.
Note that TLS 1.3 has a fixed set of ciphers, and Note that TLS 1.3 has a fixed set of ciphers, and
this list may not be respected by the remote end. this list may not be respected by the remote end.
@ -74,16 +84,82 @@ const (


It may be specified multiple times in a TLS URI. It may be specified multiple times in a TLS URI.
*/ */
TlsUriParamCipher string = "cipher" ParamCipher tlsUriParam = "cipher"
/* /*
TlsUriParamCurve specifies one (or more) curve(s) ParamCurve specifies one (or more) curve(s)
to specify for the TLS connection cipher negotiation. to specify for the TLS connection cipher negotiation.


It may be specified multiple times in a TLS URI. It may be specified multiple times in a TLS URI.
*/ */
TlsUriParamCurve string = "curve" ParamCurve tlsUriParam = "curve"
/* /*
TlsUriParamMinTls defines the minimum version of the ParamIgnoreMissing, if `1`, `yes`, `y`, or `true` indicates
that missing cert/ca/key files should not return an error if they do not exist.

Only the first defined instance is parsed.
*/
ParamIgnoreMissing tlsUriParam = "ignore_missing"
/*
ParamKey specifies a path to a private key as a PEM-encded file.

It may be PKCS#1, PKCS#8, or PEM-encoded ASN.1 DER EC key.

Supported private key types are RSA, ED25519, and ECDSA.
(Technically ECDH keys are supported, but cannot be paired with certificates
so trying to use them may result in errors or undefined behavior.
Future versions may support this as a parameter for *kex* in a TLS
*connection*, but this is unplanned at the moment.)

It may be specified multiple times in a TLS URI.
*/
ParamKey tlsUriParam = "pki_key"
/*
ParamKeylog is a way to specify the SSLKEYLOGFILE.

This parameter's value can be:

* a filepath; parent directories will attempt to be created if
they do not exist, and the file will be truncated if it exists.
The consumer/downstream is responsible for calling .Close()
on it when done.
* the special string as defined by KeyLogEnvVal to use the filepath
of whatever is set in the environment variable as defined by
KeyLogEnv (which is likely the default variable name).
It is assumed to be a filepath.
The consumer/downstream is responsible for calling .Close()
on it when done.
* the special string as defined by KeyLogBufVal to use an in-memory
buffer instead

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! DO NOT, UNDER ANY CIRCUMSTANCES, ENABLE THIS UNLESS YOU ARE !!
!! ABSOLUTELY SURE WHAT YOU ARE DOING. !!
!! IT SEVERELY COMPROMISES SECURITY !!
!! AND IS ONLY INTENDED FOR DEBUGGING PURPOSES! !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

See https://www.ietf.org/archive/id/draft-thomson-tls-keylogfile-00.html
for details.

The exact type of the returned tls.Config.KeyLogWriter will be:

* an *os.File if a filepath or the KeyLogEnvVal value was specified
* a *bytes.Buffer if the KeyLogBufVal value was specified

Only the first defined instance is parsed.
*/
ParamKeylog tlsUriParam = "debug_keylog"
/*
ParamMaxTls defines the minimum version of the
TLS protocol to use.

See ParamMinTls for syntax of the value.

Only the first defined instance is parsed.
*/
ParamMaxTls tlsUriParam = "max_tls"
/*
ParamMinTls defines the minimum version of the
TLS protocol to use. TLS protocol to use.
It is recommended to use "TLS_1.3". It is recommended to use "TLS_1.3".


@ -101,18 +177,36 @@ const (


Only the first defined instance is parsed. Only the first defined instance is parsed.
*/ */
TlsUriParamMinTls string = "min_tls" ParamMinTls tlsUriParam = "min_tls"
/* /*
TlsUriParamMaxTls defines the minimum version of the ParamMtlsCa specifies a path to a CA certificate PEM-encoded DER file.
TLS protocol to use.


See TlsUriParamMinTls for syntax of the value. Unlike ParamCa, this is explicitly used to validate clients
(see ParamMtlsMode).


Only the first defined instance is parsed. If not specified (and ParamMtlsMode is anything *but* `NoClientCert`/`0`),
the same evaluated roots/trust anchors used for ParamCa will be used.

It may be specified multiple times in a TLS URI.
*/ */
TlsUriParamMaxTls string = "max_tls" ParamMtlsCa tlsUriParam = "mtls_ca"
/* /*
TlsUriParamNet is used by TlsUri.ToConn and TlsUri.ToTlsConn to explicitly specify a network. ParamMtlsMode specifies if TLS client certificate auth should be used or not,
and what mode/type of requirement.
This is only useful if running a server/listener with mTLS auth.
Clients should leave it empty.
Servers/listeners should leave it empty if they do not wish to use
mTLS auth for clients.

The string may either be the name (as per
https://pkg.go.dev/crypto/tls#ClientAuthType)
or an int (normal, hex, etc. string representation) of the constant's value.

See also ParamMtlsCa.
*/
ParamMtlsMode tlsUriParam = "mtls_auth"
/*
ParamNet is used by TlsUri.ToConn and TlsUri.ToTlsConn to explicitly specify a network.


The default is "tcp". The default is "tcp".


@ -120,15 +214,33 @@ const (


Only the first defined instance is parsed. Only the first defined instance is parsed.
*/ */
TlsUriParamNet string = "net" ParamNet tlsUriParam = "net"
/*
ParamNoVerify, if `1`, `yes`, `y`, or `true` indicates
that the TLS connection should not require verification of
the remote end (e.g. hostname matches, trusted chain, etc.).

Any other value for this parameter will be parsed as "False"
(meaning the remote end's certificate SHOULD be verified).

Only the first defined instance is parsed.
*/
ParamNoVerify tlsUriParam = "no_verify"
/*
ParamSni indicates that the TLS connection should expect this hostname
instead of the hostname specified in the URI itself.

Only the first defined instance is parsed.
*/
ParamSni tlsUriParam = "sni"
) )


var ( var (
paramBoolValsTrue []string = []string{ paramBoolValsTrue []string = []string{
"1", "yes", "y", "true", "true", "yes", "y", "1",
} }
paramBoolValsFalse []string = []string{ paramBoolValsFalse []string = []string{
"0", "no", "n", "false", "false", "no", "n", "0",
} }
validate *validator.Validate = validator.New(validator.WithRequiredStructEnabled()) validate *validator.Validate = validator.New(validator.WithRequiredStructEnabled())
) )

45
consts_param_map.go Normal file
View File

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

/*
THIS FILE IS AUTOMATICALLY GENERATED.
DO NOT EDIT.
SEE internal/constmap/ FOR DETAILS.
*/

var (
// tlsUriParamStrMap contains a map of the constant string *name* of a tlsUriParam as mapped to its *value* (at time of generation).
tlsUriParamStrMap map[string]string = map[string]string{
"ParamCa": "pki_ca",
"ParamCert": "pki_cert",
"ParamCipher": "cipher",
"ParamCurve": "curve",
"ParamIgnoreMissing": "ignore_missing",
"ParamKey": "pki_key",
"ParamKeylog": "debug_keylog",
"ParamMaxTls": "max_tls",
"ParamMinTls": "min_tls",
"ParamMtlsCa": "mtls_ca",
"ParamMtlsMode": "mtls_auth",
"ParamNet": "net",
"ParamNoVerify": "no_verify",
"ParamSni": "sni",
}

// tlsUriStrParamMap contains a map of the *value* (at time of generation) of tlsUriParam constants to the constant string *name*.
tlsUriStrParamMap map[string]string = map[string]string{
"pki_ca": "ParamCa",
"pki_cert": "ParamCert",
"cipher": "ParamCipher",
"curve": "ParamCurve",
"ignore_missing": "ParamIgnoreMissing",
"pki_key": "ParamKey",
"debug_keylog": "ParamKeylog",
"max_tls": "ParamMaxTls",
"min_tls": "ParamMinTls",
"mtls_ca": "ParamMtlsCa",
"mtls_auth": "ParamMtlsMode",
"net": "ParamNet",
"no_verify": "ParamNoVerify",
"sni": "ParamSni",
}
)

View File

@ -5,9 +5,13 @@ import (
) )


var ( var (
ErrBadMtlsMode error = errors.New("invalid TLS client cert auth/mTLS mode/type")
ErrBadPortRange error = errors.New("invalid port; must be between 0 and 65535")
ErrBadTlsCipher error = errors.New("invalid TLS cipher suite") ErrBadTlsCipher error = errors.New("invalid TLS cipher suite")
ErrBadTlsCurve error = errors.New("invalid TLS curve") ErrBadTlsCurve error = errors.New("invalid TLS curve")
ErrBadTlsVer error = errors.New("invalid TLS version") ErrBadTlsVer error = errors.New("invalid TLS version")
ErrReqParams error = errors.New("a nil/uninitialized requestUriParams was passed")
ErrReqStruct error = errors.New("invalid type; a pointer to a struct is required")
ErrUnknownCipher error = errors.New("unknown TLS cipher") ErrUnknownCipher error = errors.New("unknown TLS cipher")
ErrUnknownKey error = errors.New("unknown key type") ErrUnknownKey error = errors.New("unknown key type")
) )

234
funcs.go
View File

@ -11,11 +11,14 @@ import (
`crypto/x509` `crypto/x509`
`encoding/pem` `encoding/pem`
`errors` `errors`
`io`
`net/url` `net/url`
`os` `os`
`path/filepath`
`strconv` `strconv`
`strings` `strings`


`r00t2.io/sysutils/envs`
`r00t2.io/sysutils/paths` `r00t2.io/sysutils/paths`
) )


@ -85,6 +88,60 @@ func IsMatchedPair(privKey crypto.PrivateKey, cert *x509.Certificate) (isMatched
return 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. 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. 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 b []byte
var rootCAs *x509.CertPool var rootCAs *x509.CertPool
var mtlsCAs *x509.CertPool
var intermediateCAs []*x509.Certificate var intermediateCAs []*x509.Certificate
var concatCAs []*x509.Certificate
var privKeys []crypto.PrivateKey var privKeys []crypto.PrivateKey
var tlsCerts []tls.Certificate var tlsCerts []tls.Certificate
var allowInvalid bool var allowInvalid bool
var ciphers []uint16 var ciphers []uint16
var curves []tls.CurveID var curves []tls.CurveID
var params map[string][]string
var ok bool var ok bool
var val string var val string
var minVer uint16 var minVer uint16
var maxVer 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 buf *bytes.Buffer = new(bytes.Buffer)
var srvNm string = tlsUri.Hostname() var srvNm string = tlsUri.Hostname()


params = tlsUri.Query() if tlsUri.Query() == nil || len(tlsUri.Query()) == 0 {

if params == nil {
tlsConf = &tls.Config{ tlsConf = &tls.Config{
ServerName: srvNm, ServerName: srvNm,
} }
return return
} }


for k, v := range tlsUri.Query() {
params[tlsUriParam(k)] = v
}

// These are all filepath(s). // These are all filepath(s).
for _, k := range []string{ for _, k := range []tlsUriParam{
TlsUriParamCa, ParamCa,
TlsUriParamCert, ParamCert,
TlsUriParamKey, ParamKey,
ParamMtlsCa,
} { } {
if _, ok = params[k]; ok { if _, ok = params[k]; ok {
for idx, _ := range params[k] { 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). // CA cert(s).
buf.Reset() buf.Reset()
if _, ok = params[TlsUriParamCa]; ok { if _, ok = params[ParamCa]; ok {
rootCAs = x509.NewCertPool() rootCAs = x509.NewCertPool()
for _, c := range params[TlsUriParamCa] { for _, c := range params[ParamCa] {
if b, err = os.ReadFile(c); err != nil { if b, err = os.ReadFile(c); err != nil {
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) && ignoreMissing {
err = nil err = nil
continue continue
} else {
return
} }
} }
buf.Write(b) 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() buf.Reset()
if _, ok = params[TlsUriParamKey]; ok { if _, ok = params[ParamKey]; ok {
for _, k := range params[TlsUriParamKey] { for _, k := range params[ParamKey] {
if b, err = os.ReadFile(k); err != nil { if b, err = os.ReadFile(k); err != nil {
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) && ignoreMissing {
err = nil err = nil
continue continue
} else { } else {
@ -482,12 +591,12 @@ func ParseTlsUri(tlsUri *url.URL) (tlsConf *tls.Config, err error) {
} }
} }


// (Client) Certificate(s). // (Leaf) Certificate(s).
buf.Reset() buf.Reset()
if _, ok = params[TlsUriParamCert]; ok { if _, ok = params[ParamCert]; ok {
for _, c := range params[TlsUriParamCert] { for _, c := range params[ParamCert] {
if b, err = os.ReadFile(c); err != nil { if b, err = os.ReadFile(c); err != nil {
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) && ignoreMissing {
err = nil err = nil
continue continue
} else { } else {
@ -496,19 +605,24 @@ func ParseTlsUri(tlsUri *url.URL) (tlsConf *tls.Config, err error) {
} }
buf.Write(b) 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 return
} }
if concatCAs != nil {
for _, ca := range concatCAs {
rootCAs.AddCert(ca)
}
}
} }


// Hostname (Override). // Hostname (Override).
if _, ok = params[TlsUriParamSni]; ok { if _, ok = params[ParamSni]; ok {
srvNm = params[TlsUriParamSni][0] srvNm = params[ParamSni][0]
} }


// Disable Verification. // Disable Verification.
if _, ok = params[TlsUriParamNoVerify]; ok { if _, ok = params[ParamNoVerify]; ok {
val = strings.ToLower(params[TlsUriParamNoVerify][0]) val = strings.ToLower(params[ParamNoVerify][0])
for _, i := range paramBoolValsTrue { for _, i := range paramBoolValsTrue {
if i == val { if i == val {
allowInvalid = true 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. // Ciphers.
if _, ok = params[TlsUriParamCipher]; ok { if _, ok = params[ParamCipher]; ok {
ciphers = ParseTlsCiphers(strings.Join(params[TlsUriParamCipher], ",")) ciphers = ParseTlsCiphers(strings.Join(params[ParamCipher], ","))
} }


// Minimum TLS Protocol Version. // Minimum TLS Protocol Version.
if _, ok = params[TlsUriParamMinTls]; ok { if _, ok = params[ParamMinTls]; ok {
if minVer, err = ParseTlsVersion(params[TlsUriParamMinTls][0]); err != nil { if minVer, err = ParseTlsVersion(params[ParamMinTls][0]); err != nil {
return return
} }
} }


// Maximum TLS Protocol Version. // Maximum TLS Protocol Version.
if _, ok = params[TlsUriParamMaxTls]; ok { if _, ok = params[ParamMaxTls]; ok {
if maxVer, err = ParseTlsVersion(params[TlsUriParamMaxTls][0]); err != nil { if maxVer, err = ParseTlsVersion(params[ParamMaxTls][0]); err != nil {
return return
} }
} }


// Curves. // Curves.
if _, ok = params[TlsUriParamCurve]; ok { if _, ok = params[ParamCurve]; ok {
curves = ParseTlsCurves(strings.Join(params[TlsUriParamCurve], ",")) curves = ParseTlsCurves(strings.Join(params[ParamCurve], ","))
} }


tlsConf = &tls.Config{ tlsConf = &tls.Config{
Certificates: tlsCerts, Certificates: tlsCerts,
RootCAs: rootCAs, RootCAs: rootCAs,
ServerName: srvNm, ServerName: srvNm,
ClientAuth: clientAuthType,
ClientCAs: mtlsCAs,
InsecureSkipVerify: allowInvalid, InsecureSkipVerify: allowInvalid,
CipherSuites: ciphers, CipherSuites: ciphers,
MinVersion: minVer, MinVersion: minVer,
MaxVersion: maxVer, MaxVersion: maxVer,
CurvePreferences: curves, CurvePreferences: curves,
KeyLogWriter: keylog,
} }


return 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. 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. 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. that certificate but no error will be returned.
This behavior can be avoided by passing a nil slice to keys. 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). 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. 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 pemBlocks []*pem.Block
var cert *x509.Certificate var cert *x509.Certificate
var certs []*x509.Certificate var certs []*x509.Certificate
var caCerts []*x509.Certificate
var parsedKeys []crypto.PrivateKey var parsedKeys []crypto.PrivateKey
var isMatched bool var isMatched bool
var foundKey crypto.PrivateKey var foundKey crypto.PrivateKey
var interBytes [][]byte var interBytes [][]byte
var skipKeyPair bool = keys == nil
var parsedKeysBuf *bytes.Buffer = new(bytes.Buffer) var parsedKeysBuf *bytes.Buffer = new(bytes.Buffer)


if pemBlocks, err = SplitPem(certRaw); err != nil { if pemBlocks, err = SplitPem(certRaw); err != nil {
@ -777,7 +925,7 @@ func ParseLeafCert(certRaw []byte, keys []crypto.PrivateKey, intermediates ...*x
break break
} }
} }
if foundKey == nil && !skipKeyPair { if foundKey == nil {
continue continue
} }
tlsCerts = append( tlsCerts = append(
@ -790,8 +938,6 @@ func ParseLeafCert(certRaw []byte, keys []crypto.PrivateKey, intermediates ...*x
) )
} }


_ = caCerts

return return
} }



173
funcs_fieldmap_tlsuri.go Normal file
View File

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

import (
`reflect`
`sync`

`r00t2.io/goutils/multierr`
`r00t2.io/goutils/structutils`
)

/*
FieldMapTlsURI returns TlsUri tu from struct, map, or slice s.

If a map, the keys should be the Param* tlsUriParam contants' *values*
(e.g. map[string][]string{"ignore_missing": []string{"true"}}).
If a slice, it should be a slice of strings (or nested other supported type)
which follow the pattern `<key>=<value>` where `<key>` is one of the
Param* tlsUriParam constants' *values*.
Obviously in both these cases, the host/port will not be set.
See the NOTE below.

The default is to recurse into e.g. nested structs and sub-structs
unless they are tagged with the value "-".

This function operates on the struct tag `tlsUri` (e.g. `tlsUri:"-"`, `tlsUri:"ca"`, etc.).
If you need to use an explicit/different struct tag name, use FieldMapTlsURIWithTag.

See the struct tags on TlsFlat for an example of usage.

NOTE: The host and port are not currently directly
configurable via struct tags. These can be set
via the SetHost, SetPort, SetPortStr, SetHostPort,
and/or SetHostPortStr on the returned TlsUri.
*/
func FieldMapTlsURI[T any](s T) (tu *TlsUri, err error) {

if tu, err = FieldMapTlsURIWithTag(s, dfltStructTag); err != nil {
return
}

return
}

// FieldMapTlsURIWithTag is exactly like FieldMapTlsURI but allows specifying an explict tag name.
func FieldMapTlsURIWithTag[T any](s T, tagName string,) (tu *TlsUri, err error) {

var ptrVal reflect.Value
var sVal reflect.Value = reflect.ValueOf(s)
var sType reflect.Type = sVal.Type()
var params *tlsUriParams

*params = make(map[tlsUriParam][]string)

if sVal.IsNil() || sVal.IsZero() || !sVal.IsValid() {
return
}
ptrVal = sVal.Elem()

if err = fieldMapTlsURIStruct(params, ptrVal, tagName); err != nil {
return
}

// TODO

return
}

// fieldMapTlsURIStruct parses a reflect.Value and populates params with any matching fields found.
func fieldMapTlsURIStruct(params *tlsUriParams, v reflect.Value, tagName string) (err error) {

var field reflect.StructField
var fieldVal reflect.Value
var wg sync.WaitGroup
var errChan chan error
var numJobs int
var doneChan chan bool = make(chan bool, 1)
var mErr *multierr.MultiError = multierr.NewMultiError(nil)
var t reflect.Type = v.Type()
var kind reflect.Kind = t.Kind()

if params == nil {
err = ErrReqParams
return
}

if kind != reflect.Struct {
err = ErrReqStruct
return
}

numJobs = v.NumField()
wg.Add(numJobs)
errChan = make(chan error, numJobs)

for i := 0; i < v.NumField(); i++ {
field = t.Field(i)
fieldVal = v.Field(i)

go func(f reflect.StructField, fv reflect.Value) {
var fErr error

defer wg.Done()

if fErr = fieldMapTlsURIStructField(params, f, fv, tagName); fErr != nil {
errChan <- fErr
return
}
}(field, fieldVal)
}

go func() {
wg.Wait()
close(errChan)
doneChan <- true
}()

<-doneChan

for i := 0; i < numJobs; i++ {
if err = <-errChan; err != nil {
mErr.AddError(err)
err = nil
}
}

if !mErr.IsEmpty() {
err = mErr
return
}

// TODO

return
}

// fieldMapTlsURIStructField parses a single struct field.
func fieldMapTlsURIStructField(params *tlsUriParams, field reflect.StructField, fv reflect.Value, tagName string) (err error) {

var parsedTagOpts map[string]bool

if params == nil {
err = ErrReqParams
return
}

if !fv.CanSet() {
return
}

// Skip if explicitly instructed to do so.
parsedTagOpts = structutils.TagToBoolMap(field, tagName, structutils.TagMapTrim)
if parsedTagOpts["-"] {
return
}

if fv.Kind() == reflect.Ptr {
err = fieldMapTlsURIStructField(params, field, fv.Elem(), tagName)
} else {
err = fieldMapTlsUriValue(params, fv, tagName)
}

// TODO

return
}

// fieldMapTlsUriValue is a type dispatcher for a reflected value.
func fieldMapTlsUriValue(params *tlsUriParams, v reflect.Value, tagName string) (err error) {

// TODO

return
}

65
funcs_reflectflat.go Normal file
View File

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

import (
`fmt`
`net/url`
`reflect`

`r00t2.io/goutils/structutils`
)

/*
reflectFlatUri is the reflection used for a TlsFlat when converting to a TlsUri.
*/
func reflectFlatUri(s *TlsFlat, tagName string) (tu *TlsUri, err error) {

var uri *url.URL
var field reflect.StructField
var fieldVal reflect.Value
var params url.Values = make(url.Values)
var sVal reflect.Value = reflect.ValueOf(s)
var sType reflect.Type = sVal.Type()
var kind reflect.Kind = sType.Kind()
var parsedTagOpts map[string]bool

if s == nil {
return
}

if uri, err = url.Parse(fmt.Sprintf("tls://%s", s.Host)); err != nil {
return
}
if s.Port != nil {
uri.Host = fmt.Sprintf("%s:%d", uri.Host, *s.Port)
}

for i := 0; i < sType.NumField(); i++ {
field = sType.Field(i)
fieldVal = sVal.Field(i)
if !fieldVal.CanSet() {
continue
}

parsedTagOpts = structutils.TagToBoolMap(field, "tlsUri")
if parsedTagOpts["-"] {
continue
}

}

// TODO

if len(params) != 0 {
if uri.Path == "" {
uri.Path = "/"
}
if uri, err = url.Parse(fmt.Sprintf("%s%s", uri.String(), params.Encode())); err != nil {
return
}
}
tu = &TlsUri{
URL: uri,
}

return
}

60
funcs_reflecturi.go Normal file
View File

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

import (
`reflect`
`strconv`
)

/*
reflectUriFlat is the reflection used for a TlsUri when converting to a TlsFlat.
*/
func reflectUriFlat(s *TlsUri, tagName string) (f *TlsFlat, err error) {

var ptrVal reflect.Value
var ptrType reflect.Type
var ptrKind reflect.Kind
var n uint64
var flat TlsFlat
var sVal reflect.Value = reflect.ValueOf(s)
var sType reflect.Type = sVal.Type()
var kind reflect.Kind = sType.Kind()
var params map[tlsUriParam][]string = make(map[tlsUriParam][]string)

if s == nil {
return
}

for k, v := range params {
if tlsUriParam(k) != "" {
params[k] = v
}
}

flat = TlsFlat{
Host: s.Hostname(),
Port: nil,
SniName: nil,
SkipVerify: false,
Certs: nil,
CaFiles: nil,
CipherSuites: nil,
Curves: nil,
MinTlsProtocol: nil,
MaxTlsProtocol: nil,
}

if s.Port() != "" {
f.Port = new(uint16)
if n, err = strconv.ParseUint(s.Port(), 10, 16); err != nil {
return
}
*f.Port = uint16(n)
}

// TODO: lookup map, map[string]tlsUriParam where key is name of constant as string, and value is that corresponding value.
// Use that value to lookup in params.

f = &flat

return
}

View File

@ -2,9 +2,117 @@ package cryptparse


import ( import (
`crypto/tls` `crypto/tls`
`embed`
`errors`
`fmt`
`io/fs`
`net/url`
`os`
`strings`
"testing" "testing"
) )


var (
// Generated from ../_extra/gen_test_pki

//go:embed "_testdata"
testPems embed.FS
testTmpPemFiles map[string]*os.File
testKt string = "ed25519"
)

func testInit(t *testing.T) (err error) {

var n string
var nkt string
var b []byte
var names []fs.DirEntry
var ok bool

if testTmpPemFiles == nil {
testTmpPemFiles = make(map[string]*os.File)
}

if names, err = testPems.ReadDir("_testdata"); err != nil {
return
}
// only ".keep" is present.
if len(names) == 1 {
t.Fatalf(
"There aren't any test PEMs."+
"You must `go run *.go` in _extras/gen_test_pki and copy the %s PEMs into _testdata.",
testKt,
)
}

// populate tmpFiles from the embed.FS `pems` and write out to temp files.
for _, p := range []string{
"ca",
"inter",
"leaf_server",
"leaf_user",
} {
for _, pt := range []string{
"cert",
"csr",
"key",
} {
n = fmt.Sprintf("%s_%s", p, pt)
nkt = fmt.Sprintf("%s_%s_%s", p, testKt, pt)
if _, ok = testTmpPemFiles[n]; !ok {
if b, err = testPems.ReadFile(fmt.Sprintf("_testdata/%s.pem", nkt)); err != nil {
t.Fatalf("Read '%s' failed: %v", nkt, err)
}
if testTmpPemFiles[n], err = os.CreateTemp("", fmt.Sprintf(".*.%s.pem", n)); err != nil {
t.Fatalf("Create temp file for %s failed: %v", n, err)
}
if _, err = testTmpPemFiles[n].Write(b); err != nil {
t.Fatalf("Write to %s failed: %v", n, err)
}
if err = testTmpPemFiles[n].Close(); err != nil {
t.Fatalf("Closing %s failed: %v", n, err)
}
}
}
if strings.HasPrefix(p, "leaf_") {
n = fmt.Sprintf("%s_chained", p)
nkt = fmt.Sprintf("%s_%s_cert_chained.pem", p, testKt)
if _, ok = testTmpPemFiles[n]; !ok {
if b, err = testPems.ReadFile(fmt.Sprintf("_testdata/%s.pem", nkt)); err != nil {
t.Fatalf("Read '%s' failed: %v", nkt, err)
}
if testTmpPemFiles[n], err = os.CreateTemp("", fmt.Sprintf(".*.%s.pem", n)); err != nil {
t.Fatalf("Create temp file for %s failed: %v", n, err)
}
if _, err = testTmpPemFiles[n].Write(b); err != nil {
t.Fatalf("Write to %s failed: %v", n, err)
}
if err = testTmpPemFiles[n].Close(); err != nil {
t.Fatalf("Closing %s failed: %v", n, err)
}
}
}
}

t.Cleanup(func() {
var cErr error
for k, f := range testTmpPemFiles {
if cErr = f.Close(); cErr != nil && !errors.Is(cErr, os.ErrClosed) {
t.Logf("Error when closing %s '%s': %v", k, f.Name(), cErr)
cErr = nil
} else if cErr != nil {
cErr = nil
}
if cErr = os.Remove(f.Name()); cErr != nil {
t.Logf("Error when removing %s '%s': %v", k, f.Name(), cErr)
cErr = nil
}
}
})

return
}

func TestCiphers(t *testing.T) { func TestCiphers(t *testing.T) {


var err error var err error
@ -35,3 +143,32 @@ func TestCiphers(t *testing.T) {


_ = cs _ = cs
} }

func TestTlsUri(t *testing.T) {

var err error
var uStr string
var u *url.URL
var tlsU *TlsUri

if err = testInit(t); err != nil {
t.Fatal(err)
}
uStr = fmt.Sprintf(
"https://:9091/?"+
"pki_ca=%s&"+ // testTmpFiles["ca_cert"]
"pki_cert=%s&"+ // testTmpFiles["leaf_server_chained"]
"pki_key=%s&"+ // testTmpFiles["leaf_server_key"]
"min_tls=1.2&max_tls=1.2&"+
"sni=server.example.com",
testTmpPemFiles["ca_cert"], testTmpPemFiles["leaf_server_chained"], testTmpPemFiles["leaf_server_key"],
)
if u, err = url.Parse(uStr); err != nil {
t.Fatalf("Failed to parse URL string '%s': %v", uStr, err)
}

tlsU = &TlsUri{
URL: u,
}

}

View File

@ -57,6 +57,7 @@ func (t *TlsFlat) ToTlsConfig() (tlsConf *tls.Config, err error) {
var curves []tls.CurveID var curves []tls.CurveID
var minVer uint16 var minVer uint16
var maxVer uint16 var maxVer uint16
var concatCAs []*x509.Certificate
var buf *bytes.Buffer = new(bytes.Buffer) var buf *bytes.Buffer = new(bytes.Buffer)
var srvNm string = t.SniName var srvNm string = t.SniName


@ -107,10 +108,15 @@ func (t *TlsFlat) ToTlsConfig() (tlsConf *tls.Config, err error) {
if b, err = os.ReadFile(c.CertFile); err != nil { if b, err = os.ReadFile(c.CertFile); err != nil {
return return
} }
if parsedTlsCerts, err = ParseLeafCert(b, privKeys, intermediateCAs...); err != nil { if parsedTlsCerts, concatCAs, err = ParseLeafCert(b, privKeys, intermediateCAs...); err != nil {
return return
} }
tlsCerts = append(tlsCerts, parsedTlsCerts...) tlsCerts = append(tlsCerts, parsedTlsCerts...)
if concatCAs != nil {
for _, ca := range concatCAs {
rootCAs.AddCert(ca)
}
}
} }
} }


@ -163,49 +169,49 @@ func (t *TlsFlat) ToTlsUri() (tlsUri *TlsUri, err error) {
// CA cert(s). // CA cert(s).
if t.CaFiles != nil { if t.CaFiles != nil {
for _, c := range t.CaFiles { for _, c := range t.CaFiles {
u.Query().Add(TlsUriParamCa, c) u.Query().Add(ParamCa, c)
} }
} }


// Keys and Certs. // Keys and Certs.
if t.Certs != nil { if t.Certs != nil {
for _, c := range t.Certs { for _, c := range t.Certs {
u.Query().Add(TlsUriParamCert, c.CertFile) u.Query().Add(ParamCert, c.CertFile)
if c.KeyFile != nil { if c.KeyFile != nil {
u.Query().Add(TlsUriParamKey, *c.KeyFile) u.Query().Add(ParamKey, *c.KeyFile)
} }
} }
} }


// Enforce the SNI hostname. // Enforce the SNI hostname.
u.Query().Add(TlsUriParamSni, t.SniName) u.Query().Add(ParamSni, t.SniName)


// Disable Verification. // Disable Verification.
if t.SkipVerify { if t.SkipVerify {
u.Query().Add(TlsUriParamNoVerify, "1") u.Query().Add(ParamNoVerify, "1")
} }


// Ciphers. // Ciphers.
if t.CipherSuites != nil { if t.CipherSuites != nil {
for _, c := range t.CipherSuites { for _, c := range t.CipherSuites {
u.Query().Add(TlsUriParamCipher, c) u.Query().Add(ParamCipher, c)
} }
} }


// Minimum TLS Protocol Version. // Minimum TLS Protocol Version.
if t.MinTlsProtocol != nil { if t.MinTlsProtocol != nil {
u.Query().Add(TlsUriParamMinTls, *t.MinTlsProtocol) u.Query().Add(ParamMinTls, *t.MinTlsProtocol)
} }


// Maximum TLS Protocol Version. // Maximum TLS Protocol Version.
if t.MaxTlsProtocol != nil { if t.MaxTlsProtocol != nil {
u.Query().Add(TlsUriParamMaxTls, *t.MaxTlsProtocol) u.Query().Add(ParamMaxTls, *t.MaxTlsProtocol)
} }


// Curves. // Curves.
if t.Curves != nil { if t.Curves != nil {
for _, c := range t.Curves { for _, c := range t.Curves {
u.Query().Add(TlsUriParamCurve, c) u.Query().Add(ParamCurve, c)
} }
} }



223
funcs_tlsflat.go.old Normal file
View File

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

import (
`bytes`
`crypto`
`crypto/tls`
`crypto/x509`
`errors`
`fmt`
`net/url`
`os`
`strings`

`r00t2.io/sysutils/paths`
)

// Normalize ensures that all specified filepaths are absolute, etc.
func (t *TlsFlat) Normalize() (err error) {

if t.Certs != nil {
for _, c := range t.Certs {
if err = paths.RealPath(&c.CertFile); err != nil {
return
}
if c.KeyFile != nil {
if err = paths.RealPath(c.KeyFile); err != nil {
return
}
}
}
}
if t.CaFiles != nil {
for idx, _ := range t.CaFiles {
if err = paths.RealPath(&t.CaFiles[idx]); err != nil {
return
}
}
}

return
}

/*
ToTlsConfig returns a tls.Config from a TlsFlat. Note that it will have Normalize called on it.

Unfortunately it's not possible for this library to do the reverse, as CA certificates are not able to be extracted from an x509.CertPool.
*/
func (t *TlsFlat) ToTlsConfig() (tlsConf *tls.Config, err error) {

var b []byte
var rootCAs *x509.CertPool
var intermediateCAs []*x509.Certificate
var privKeys []crypto.PrivateKey
var tlsCerts []tls.Certificate
var parsedTlsCerts []tls.Certificate
var ciphers []uint16
var curves []tls.CurveID
var minVer uint16
var maxVer uint16
var concatCAs []*x509.Certificate
var buf *bytes.Buffer = new(bytes.Buffer)
var srvNm string = t.SniName

// Normalize any filepaths before validation.
if err = t.Normalize(); err != nil {
return
}

// And validate.
if err = validate.Struct(t); err != nil {
return
}

// CA cert(s).
buf.Reset()
if t.CaFiles != nil {
rootCAs = x509.NewCertPool()
for _, c := range t.CaFiles {
if b, err = os.ReadFile(c); err != nil {
if errors.Is(err, os.ErrNotExist) {
err = nil
continue
}
}
buf.Write(b)
}
if rootCAs, _, intermediateCAs, err = ParseCA(buf.Bytes()); err != nil {
return
}
} else {
if rootCAs, err = x509.SystemCertPool(); err != nil {
return
}
}

// Keys and Certs. They are assumed to be matched.
if t.Certs != nil {
for _, c := range t.Certs {
privKeys = nil
if c.KeyFile != nil {
if b, err = os.ReadFile(*c.KeyFile); err != nil {
return
}
if privKeys, err = ParsePrivateKey(b); err != nil {
return
}
}
if b, err = os.ReadFile(c.CertFile); err != nil {
return
}
if parsedTlsCerts, concatCAs, err = ParseLeafCert(b, privKeys, intermediateCAs...); err != nil {
return
}
tlsCerts = append(tlsCerts, parsedTlsCerts...)
if concatCAs != nil {
for _, ca := range concatCAs {
rootCAs.AddCert(ca)
}
}
}
}

// Ciphers.
if t.CipherSuites != nil {
ciphers = ParseTlsCiphers(strings.Join(t.CipherSuites, ","))
}

// Minimum TLS Protocol Version.
if t.MinTlsProtocol != nil {
if minVer, err = ParseTlsVersion(*t.MinTlsProtocol); err != nil {
return
}
}

// Maximum TLS Protocol Version.
if t.MaxTlsProtocol != nil {
if maxVer, err = ParseTlsVersion(*t.MaxTlsProtocol); err != nil {
return
}
}

// Curves.
if t.Curves != nil {
curves = ParseTlsCurves(strings.Join(t.Curves, ","))
}

tlsConf = &tls.Config{
Certificates: tlsCerts,
RootCAs: rootCAs,
ServerName: srvNm,
InsecureSkipVerify: t.SkipVerify,
CipherSuites: ciphers,
MinVersion: minVer,
MaxVersion: maxVer,
CurvePreferences: curves,
}
return
}

// ToTlsUri returns a TlsUri from a TlsFlat.
func (t *TlsFlat) ToTlsUri() (tlsUri *TlsUri, err error) {

var u *url.URL

if u, err = url.Parse(fmt.Sprintf("tls://%v/", t.SniName)); err != nil {
return
}

// CA cert(s).
if t.CaFiles != nil {
for _, c := range t.CaFiles {
u.Query().Add(ParamCa, c)
}
}

// Keys and Certs.
if t.Certs != nil {
for _, c := range t.Certs {
u.Query().Add(ParamCert, c.CertFile)
if c.KeyFile != nil {
u.Query().Add(ParamKey, *c.KeyFile)
}
}
}

// Enforce the SNI hostname.
u.Query().Add(ParamSni, t.SniName)

// Disable Verification.
if t.SkipVerify {
u.Query().Add(ParamNoVerify, "1")
}

// Ciphers.
if t.CipherSuites != nil {
for _, c := range t.CipherSuites {
u.Query().Add(ParamCipher, c)
}
}

// Minimum TLS Protocol Version.
if t.MinTlsProtocol != nil {
u.Query().Add(ParamMinTls, *t.MinTlsProtocol)
}

// Maximum TLS Protocol Version.
if t.MaxTlsProtocol != nil {
u.Query().Add(ParamMaxTls, *t.MaxTlsProtocol)
}

// Curves.
if t.Curves != nil {
for _, c := range t.Curves {
u.Query().Add(ParamCurve, c)
}
}

tlsUri = &TlsUri{
URL: u,
}

return
}

View File

@ -3,12 +3,108 @@ package cryptparse
import ( import (
`crypto` `crypto`
`crypto/tls` `crypto/tls`
`fmt`
`net` `net`
`net/url` `net/url`
`os` `os`
`strconv`
`strings` `strings`
) )


/*
SetHost allows one to explicitly set the host component of the URI.
A host can be removed from a TlsUri by invoking this method with an empty hostAddr string.

No validation is performed.
*/
func (t *TlsUri) SetHost(hostAddr string) {

if t == nil {
return
}

if t.Port() == "" {
t.Host = hostAddr
} else {
t.Host = fmt.Sprintf("%s:%s", hostAddr, t.Port())
}

return
}

/*
SetHostPort is a small wrapper around the SetHost and SetPort methods, combining them into one.

Refer to the comments for each on usage.
*/
func (t *TlsUri) SetHostPort(host string, port *uint16) {

t.SetPort(port)
t.SetHost(host)

return
}

/*
SetHostPortStr is a small wrapper around the SetHost and SetPortStr methods, combining them into one.

Refer to the comments for each on usage.
*/
func (t *TlsUri) SetHostPortStr(host string, port string) (err error) {

if err = t.SetPortStr(port); err != nil {
return
}
t.SetHost(host)

return
}

/*
SetPort allows one to explicitly set the port component of the URI.
A port can be removed from a TlsUri by invoking this method with a nil port.
*/
func (t *TlsUri) SetPort(port *uint16) {

if t == nil {
return
}

if port == nil {
t.Host = t.Hostname()
} else {
t.Host = fmt.Sprintf("%s:%d", t.Hostname(), *port)
}

return
}

/*
SetPortStr allows one to specify the port number as a string instead of a uint16 ptr.
If port is an empty string, any existing defined port will be removed from t.
*/
func (t *TlsUri) SetPortStr(port string) (err error) {

var n uint64
var u uint16

if port == "" {
t.Host = t.Hostname()
} else {
if n, err = strconv.ParseUint(port, 10, 16); err == nil {
return
}
if n > 65535 {
err = ErrBadPortRange
return
}
u = uint16(n)
t.Host = fmt.Sprintf("%s:%d", t.Hostname(), u)
}

return
}

/* /*
WithConn returns a (crypto/)tls.Conn from an existing/already dialed net.Conn. WithConn returns a (crypto/)tls.Conn from an existing/already dialed net.Conn.


@ -45,8 +141,8 @@ func (t *TlsUri) ToConn() (conn net.Conn, err error) {
params = t.Query() params = t.Query()


if params != nil { if params != nil {
if _, ok = params[TlsUriParamNet]; ok { if _, ok = params[ParamNet]; ok {
netType = params[TlsUriParamNet][0] netType = params[ParamNet][0]
} }
} }
netType = strings.ToLower(netType) netType = strings.ToLower(netType)
@ -99,8 +195,8 @@ func (t *TlsUri) ToTlsConn() (conn *tls.Conn, err error) {
params = t.Query() params = t.Query()


if params != nil { if params != nil {
if _, ok = params[TlsUriParamNet]; ok { if _, ok = params[ParamNet]; ok {
netType = params[TlsUriParamNet][0] netType = params[ParamNet][0]
} }
} }
netType = strings.ToLower(netType) netType = strings.ToLower(netType)
@ -124,7 +220,6 @@ func (t *TlsUri) ToTlsFlat() (tlsFlat *TlsFlat, err error) {


var b []byte var b []byte
var params url.Values var params url.Values
var paramMap map[string][]string
// These also have maps so they can backmap filenames. // These also have maps so they can backmap filenames.
var privKeys []crypto.PrivateKey var privKeys []crypto.PrivateKey
var privKeyMap map[string][]crypto.PrivateKey var privKeyMap map[string][]crypto.PrivateKey
@ -133,6 +228,8 @@ func (t *TlsUri) ToTlsFlat() (tlsFlat *TlsFlat, err error) {
var isMatch bool var isMatch bool
var fCert *TlsFlatCert var fCert *TlsFlatCert
var val string var val string
var ok bool
var paramMap map[tlsUriParam][]string = make(map[tlsUriParam][]string)
var f TlsFlat = TlsFlat{ var f TlsFlat = TlsFlat{
SniName: t.Hostname(), SniName: t.Hostname(),
SkipVerify: false, SkipVerify: false,
@ -145,22 +242,24 @@ func (t *TlsUri) ToTlsFlat() (tlsFlat *TlsFlat, err error) {
} }


params = t.Query() params = t.Query()
paramMap = params

if params == nil { if params == nil {
tlsFlat = &f tlsFlat = &f
return return
} }


// CA cert(s). for k, v := range params {
if t.Query().Has(TlsUriParamCa) { paramMap[tlsUriParam(k)] = v
f.CaFiles = append(f.CaFiles, paramMap[TlsUriParamCa]...)
} }


// Keys and Certs. These are done first so we can match to a client certificate. // CA cert(s).
if t.Query().Has(TlsUriParamKey) { if _, ok = paramMap[ParamCa]; ok {
f.CaFiles = append(f.CaFiles, paramMap[ParamCa]...)
}

// Keys and Certs. These are done first so we can match to a leaf certificate.
if _, ok = paramMap[ParamKey]; ok {
privKeyMap = make(map[string][]crypto.PrivateKey) privKeyMap = make(map[string][]crypto.PrivateKey)
for _, kFile := range paramMap[TlsUriParamKey] { for _, kFile := range paramMap[ParamKey] {
if b, err = os.ReadFile(kFile); err != nil { if b, err = os.ReadFile(kFile); err != nil {
return return
} }
@ -170,13 +269,13 @@ func (t *TlsUri) ToTlsFlat() (tlsFlat *TlsFlat, err error) {
privKeys = append(privKeys, privKeyMap[kFile]...) privKeys = append(privKeys, privKeyMap[kFile]...)
} }
} }
if t.Query().Has(TlsUriParamCert) { if t_, ok = paramMap[ParamCert]; ok {
tlsCertMap = make(map[string][]tls.Certificate) tlsCertMap = make(map[string][]tls.Certificate)
for _, cFile := range paramMap[TlsUriParamCert] { for _, cFile := range paramMap[ParamCert] {
if b, err = os.ReadFile(cFile); err != nil { if b, err = os.ReadFile(cFile); err != nil {
return return
} }
if tlsCertMap[cFile], err = ParseLeafCert(b, privKeys); err != nil { if tlsCertMap[cFile], _, err = ParseLeafCert(b, privKeys); err != nil {
return return
} }
tlsCerts = append(tlsCerts, tlsCertMap[cFile]...) tlsCerts = append(tlsCerts, tlsCertMap[cFile]...)
@ -201,13 +300,13 @@ func (t *TlsUri) ToTlsFlat() (tlsFlat *TlsFlat, err error) {
} }


// Hostname. // Hostname.
if t.Query().Has(TlsUriParamSni) { if t.Query().Has(ParamSni) {
f.SniName = t.Query().Get(TlsUriParamSni) f.SniName = t.Query().Get(ParamSni)
} }


// Disable verification. // Disable verification.
if t.Query().Has(TlsUriParamNoVerify) { if t.Query().Has(ParamNoVerify) {
val = strings.ToLower(t.Query().Get(TlsUriParamNoVerify)) val = strings.ToLower(t.Query().Get(ParamNoVerify))
for _, i := range paramBoolValsTrue { for _, i := range paramBoolValsTrue {
if val == i { if val == i {
f.SkipVerify = true f.SkipVerify = true
@ -217,25 +316,25 @@ func (t *TlsUri) ToTlsFlat() (tlsFlat *TlsFlat, err error) {
} }


// Ciphers. // Ciphers.
if t.Query().Has(TlsUriParamCipher) { if t.Query().Has(ParamCipher) {
f.CipherSuites = params[TlsUriParamCipher] f.CipherSuites = params[ParamCipher]
} }


// Minimum TLS Protocol Version. // Minimum TLS Protocol Version.
if t.Query().Has(TlsUriParamMinTls) { if t.Query().Has(ParamMinTls) {
f.MinTlsProtocol = new(string) f.MinTlsProtocol = new(string)
*f.MinTlsProtocol = t.Query().Get(TlsUriParamMinTls) *f.MinTlsProtocol = t.Query().Get(ParamMinTls)
} }


// Maximum TLS Protocol Version. // Maximum TLS Protocol Version.
if t.Query().Has(TlsUriParamMaxTls) { if t.Query().Has(ParamMaxTls) {
f.MaxTlsProtocol = new(string) f.MaxTlsProtocol = new(string)
*f.MaxTlsProtocol = t.Query().Get(TlsUriParamMaxTls) *f.MaxTlsProtocol = t.Query().Get(ParamMaxTls)
} }


// Curves. // Curves.
if t.Query().Has(TlsUriParamCurve) { if t.Query().Has(ParamCurve) {
f.Curves = params[TlsUriParamCurve] f.Curves = params[ParamCurve]
} }


tlsFlat = &f tlsFlat = &f

24
go.mod Normal file
View File

@ -0,0 +1,24 @@
module r00t2.io/cryptparse

go 1.23.2

require (
github.com/Luzifer/go-dhparam v1.3.0
github.com/davecgh/go-spew v1.1.1
github.com/go-playground/validator/v10 v10.22.1
github.com/oriser/regroup v0.0.0-20240925165441-f6bb0e08289e
r00t2.io/sysutils v1.7.1
)

require (
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
r00t2.io/goutils v1.6.0 // indirect
)

42
go.sum Normal file
View File

@ -0,0 +1,42 @@
github.com/Luzifer/go-dhparam v1.3.0 h1:GyB+YSU2jpUbCR9SjvT8W575BPeLzd2Tt2/3BirUFKM=
github.com/Luzifer/go-dhparam v1.3.0/go.mod h1:zOdP6tT8XK6Gndh6p0GrvOpUaFawGLSzT6+n1CHY9Hk=
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=
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/oriser/regroup v0.0.0-20240925165441-f6bb0e08289e h1:cL0lMYYEbfEUBghQd4ytnl8B8Ktdm+JremTyAagegZ0=
github.com/oriser/regroup v0.0.0-20240925165441-f6bb0e08289e/go.mod h1:tUOeYZJlwO7jSmM5ko1jTCiQaWQMvh58IENEfjwYzh8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
r00t2.io/goutils v1.6.0 h1:oBC6PgBv0y/fdHeCmWgORHpBiU8uWw7IfFQJX5rIuzY=
r00t2.io/goutils v1.6.0/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o=
r00t2.io/sysutils v1.7.1 h1:OXvzcpGC+WCHusDKCZqsDqUYr73qBbWqt6mM/qY/OHs=
r00t2.io/sysutils v1.7.1/go.mod h1:Sk/7riJp9fteeW9STkdQ/k22huL1J6r05n6wLh5byHY=

View File

@ -0,0 +1,32 @@
package main

import (
_ `embed`
`text/template`

`github.com/oriser/regroup`
)

const (
pfx string = "Param"
matchType string = "tlsUriParam"
)

var (
//go:embed "tpl/consts_param_map.go.tpl"
constmapTplBytes []byte
tpl = template.Must(template.New("consts").Parse(string(constmapTplBytes)))
)

var (
// If we restructure, these paths will need to be changed.
// constsPath string = filepath.Join("..", "..", "consts.go")
// outPath string = filepath.Join("..", "..", "consts_param_map.go")
constsPath string = "consts.go"
outPath string = "consts_param_map.go"
)

var (
// The most complex part about this pattern is it has to quote the backticks as their own string addition.
stripQuotesPtrn *regroup.ReGroup = regroup.MustCompile(`^(` + "`" + `(?P<m1>.+)` + "`" + `|"(?P<m2>.+)")$`)
)

View File

@ -0,0 +1,94 @@
package main

import (
`errors`
`go/ast`
`go/token`
`log`
`strings`

`github.com/oriser/regroup`
)

/*
getParamSpec takes an ast.Decl d and returns a slice of
ParamConst found in it.

If no ParamConst are found, foundParams will be nil.
*/
func getValueSpec(d ast.Decl) (foundParams []*ParamConst) {

var ok bool
var idx int
var gd *ast.GenDecl
var spec ast.Spec
var vs *ast.ValueSpec
var vsId *ast.Ident
var nm *ast.Ident
var bl *ast.BasicLit

if gd, ok = d.(*ast.GenDecl); !ok || gd.Tok != token.CONST {
return
}

for _, spec = range gd.Specs {
if vs, ok = spec.(*ast.ValueSpec); !ok {
continue
}
if vs.Type != nil {
if vsId, ok = vs.Type.(*ast.Ident); !ok || vsId.Name != matchType {
continue
}
}
for idx, nm = range vs.Names {
if !strings.HasPrefix(nm.Name, pfx) {
continue
}
if bl, ok = vs.Values[idx].(*ast.BasicLit); !ok {
continue
}

foundParams = append(
foundParams,
&ParamConst{
ConstName: nm.Name,
// UriParamName: bl.Value,
UriParamName: stripQuotes(bl.Value),
},
)
}
}

return
}

/*
stripQuotes removes Golang-AST defining quotes.
This probably doesn't work for multiline, but should be fine for our purposes.
*/
func stripQuotes(inStr string) (outStr string) {

var err error
var matches map[string]string
var nomchErr *regroup.NoMatchFoundError = new(regroup.NoMatchFoundError)

outStr = inStr

if matches, err = stripQuotesPtrn.Groups(inStr); err != nil {
if errors.As(err, &nomchErr) {
err = nil
return
} else {
log.Panicln(err)
}
}

for _, v := range matches {
if v != "" {
outStr = v
return
}
}

return
}

57
internal/constmap/main.go Normal file
View File

@ -0,0 +1,57 @@
package main

import (
`bytes`
`fmt`
`go/ast`
`go/parser`
`go/token`
`log`
`os`

`r00t2.io/sysutils/paths`
)

/*
DO NOT RUN THIS ANYWHERE BUT FROM WHERE <r00t2.io/cryptparse>/consts.go IS LOCATED.
*/

// I *cannot believe* a library does not exist that will do this for me.

func main() {
var err error
var tfs *token.FileSet
var af *ast.File
var foundParams []*ParamConst
var paramConsts []*ParamConst
var buf *bytes.Buffer = new(bytes.Buffer)

if err = paths.RealPath(&constsPath); err != nil {
return
}
if err = paths.RealPath(&outPath); err != nil {
return
}

tfs = token.NewFileSet()

if af, err = parser.ParseFile(tfs, constsPath, nil, parser.AllErrors|parser.ParseComments); err != nil {
log.Panicln(err)
}

for _, d := range af.Decls {
if foundParams = getValueSpec(d); foundParams == nil {
continue
}
paramConsts = append(paramConsts, foundParams...)
}

if err = tpl.Execute(buf, paramConsts); err != nil {
log.Panicln(err)
}

if err = os.WriteFile(outPath, buf.Bytes(), 0644); err != nil {
log.Panicln(err)
}
fmt.Printf("++ Generated %s ++\n", outPath)
}

View File

@ -0,0 +1,24 @@
{{- /*gotype: r00t2.io/cryptparse/internal/constmap.ParamConsts*/ -}}
package cryptparse

/*
THIS FILE IS AUTOMATICALLY GENERATED.
DO NOT EDIT.
SEE internal/constmap/ FOR DETAILS.
*/

var (
// tlsUriParamStrMap contains a map of the constant string *name* of a tlsUriParam as mapped to its *value* (at time of generation).
tlsUriParamStrMap map[string]string = map[string]string{
{{- range $p := . }}
{{ printf "%#v" $p.ConstName }}: {{ printf "%#v" $p.UriParamName }},
{{- end }}
}

// tlsUriStrParamMap contains a map of the *value* (at time of generation) of tlsUriParam constants to the constant string *name*.
tlsUriStrParamMap map[string]string = map[string]string{
{{- range $p := . }}
{{ printf "%#v" $p.UriParamName }}: {{ printf "%#v" $p.ConstName }},
{{- end }}
}
)

View File

@ -0,0 +1,8 @@
package main

type ParamConsts []*ParamConst

type ParamConst struct {
ConstName string
UriParamName string
}

4
tlsuri_test.go Normal file
View File

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

// TODO; see _testdata
// need to cover intermediates

View File

@ -11,40 +11,76 @@ import (
`github.com/Luzifer/go-dhparam` `github.com/Luzifer/go-dhparam`
) )


// tlsUriParam is an unexported type used to define TlsUri parameter names (and thus tags).
type tlsUriParam string

// tlsUriParams is a collection of tlsUriParam and their value(s).
type tlsUriParams map[tlsUriParam][]string

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


// TlsFlat provides an easy structure to marshal/unmarshal a tls.Config from/to a data structure (JSON, XML, etc.). // TlsFlat provides an easy structure to marshal/unmarshal a tls.Config and/or a TlsUri from/to a data structure (JSON, XML, etc.).
type TlsFlat struct { type TlsFlat struct {
XMLName xml.Name `xml:"tlsConfig" json:"-" yaml:"-" toml:"-"` XMLName xml.Name `xml:"tlsConfig" json:"-" yaml:"-" toml:"-"`
// SniName represents the expected Server Name Indicator's name. See TlsUriParamSni. // Host is the host name. It may or may not be the same as SniName, and may be an empty string.
SniName string `json:"sni_name" toml:"SNIName" yaml:"SNI Name" xml:"sniName,attr" required:"true" validate:"required"` Host string `json:"host,omitempty" toml:"Host,omitempty" yaml:"Host,omitempty" xml:"host,attr,omitempty" tlsUri:"-"` // No reflection is done as it's directly managed.
// SkipVerify, if true, will bypass certificate verification. You generally should not enable this. See TlsUriParamNoVerify. // Port is the port number, if specified. Only relevant for listeners/clients and TlsUri.
Port *uint16 `json:"port,omitempty" toml:"Port,omitempty" yaml:"Port,omitempty" xml:"port,attr,omitempty" tlsUri:"-"` // No reflection is done as it's directly managed.
// CaFiles contains filepaths to CA certificates/"trust anchors" in PEM format. They may be combined. See ParamCa.
CaFiles []string `json:"ca_files,omitempty" toml:"CaFiles,omitempty" yaml:"CA Files,omitempty" xml:"roots>ca,omitempty" tlsUri:"ParamCa" validate:"omitempty,dive,filepath"`
// Certs contains 0 or more TlsFlatCert certificate definitions. See ParamCert and ParamKey as well.
Certs []*TlsFlatCert `json:"certs,omitempty" toml:"Certs,omitempty" yaml:"Certificates,omitempty" xml:"certs>cert,omitempty" validate:"omitempty,dive"`
// CipherSuites represents desired ciphers/cipher suites for this TLS environment. See ParamCipher.
CipherSuites []string `json:"cipher_suites,omitempty" toml:"CipherSuites,omitempty" yaml:"Cipher Suites,omitempty" xml:"ciphers,omitempty" tlsUri:"ParamCipher" validate:"omitempty,dive"`
// Curves specifies desired cryptographic curves to be used. See ParamCurve.
Curves []string `json:"curves,omitempty" toml:"Curves,omitempty" yaml:"Curves,omitempty" xml:"curves>curve,omitempty" tlsUri:"ParamCurve" validate:"omitempty,dive"`
// IgnoreMissing, if true, specifies that missing files should be ignored instead of throwing an error.
IgnoreMissing bool `json:"ignore_missing,omitempty" toml:"IgnoreMissing,omitempty" yaml:"Ignore Missing,omitempty" xml:"ignoreMissing,attr,omitempty" tlsUri:"ParamIgnoreMissing"`
/*
Keylog specifies an SSLKEYLOGFILE.

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! DO NOT, UNDER ANY CIRCUMSTANCES, ENABLE THIS UNLESS YOU ARE !!
!! ABSOLUTELY SURE WHAT YOU ARE DOING. !!
!! IT SEVERELY COMPROMISES SECURITY !!
!! AND IS ONLY INTENDED FOR DEBUGGING PURPOSES! !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

See ParamKeylog for details and special values.
*/
Keylog *string `json:"keylog,omitempty" toml:"Keylog,omitempty" yaml:"Keylog,omitempty" xml:"keylog,attr,omitempty" validate:"omitempty,dive"`
// MaxTlsProtocol specifies the maximum TLS version. See ParamMaxTls.
MaxTlsProtocol *string `json:"max_tls_protocol,omitempty" xml:"maxTlsProtocol,attr,omitempty" yaml:"MaxTlsProtocol,omitempty" toml:"MaxTlsProtocol,omitempty" tlsUri:"ParamMaxTls"`
// MinTlsProtocol specifies the minimum TLS version. See ParamMinTls.
MinTlsProtocol *string `json:"min_tls_protocol,omitempty" xml:"minTlsProtocol,attr,omitempty" yaml:"MinTlsProtocol,omitempty" toml:"MinTlsProtocol,omitempty" tlsUri:"ParamMinTls"`
// MutualTlsCAs specify path(s) to CA certificates/"trust anchors" in PEM format. See ParamMtlsCa.
MutualTlsCAs []string `json:"mtls_ca,omitempty" toml:"mTLSRoots,omitempty" yaml:"MTLS CA Files,omitempty" xml:"mTlsRoots>ca,omitempty" tlsUri:"ParamMtlsCa"`
// MutualTls specifies mutual TLS and, if enabled, what type/mode/level of required validation. See ParamMtlsMode.
MutualTls *string `json:"mtls_auth" toml:"mTLS,omitempty" yaml:"mTLS Type,omitempty" xml:"mtlsAuth,attr,omitempty" tlsUri:"ParamMtlsMode"`
// NetMode is the "network type" as found in e.g. net.Dial. See ParamNet for details.
NetMode *string
// SkipVerify, if true, will bypass certificate verification. You generally should not enable this. See ParamNoVerify.
SkipVerify bool `json:"skip_verify,omitempty" toml:"SkipVerify,omitempty" yaml:"Skip Verification,omitempty" xml:"skipVerify,attr,omitempty"` 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"` SniName represents the expected Server Name Indicator's name. If not nil, Host will be used to connect/listen
// CaFiles contains filepaths to CA certificates/"trust anchors" in PEM format. They may be combined. See TlsUriParamCa. and this name will be used for certificate validation/verification.
CaFiles []string `json:"ca_files,omitempty" toml:"CaFiles,omitempty" yaml:"CA Files,omitempty" xml:"roots>ca,omitempty" validate:"omitempty,dive,filepath"` See ParamSni.
// 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"` SniName *string `json:"sni_name" toml:"SNIName" yaml:"SNI Name" xml:"sniName,attr" tlsUri:"ParamSni" required:"true" validate:"required"`
// 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). // TlsFlatCert represents a certificate (and, possibly, paired key).
type TlsFlatCert struct { type TlsFlatCert struct {
XMLName xml.Name `xml:"cert" json:"-" yaml:"-" toml:"-"` XMLName xml.Name `xml:"cert" json:"-" yaml:"-" toml:"-"`
// KeyFile is a filepath to a PEM-encoded key file. See TlsUriParamKey. // KeyFile is a filepath to a PEM-encoded key file. See ParamKey.
KeyFile *string `json:"key,omitempty" xml:"key,attr,omitempty" yaml:"Key,omitempty" toml:"Key,omitempty" validate:"omitempty,filepath"` KeyFile *string `json:"key,omitempty" xml:"key,attr,omitempty" yaml:"Key,omitempty" toml:"Key,omitempty" tlsUri:"ParamKey" validate:"omitempty,filepath"`
// CertFile is a filepath to a PEM-encoded certificate file. See TlsUriParamCert. // CertFile is a filepath to a PEM-encoded certificate file. See ParamCert.
CertFile string `json:"cert" xml:",chardata" yaml:"Certificate" toml:"Certificate" required:"true" validate:"required,filepath"` CertFile string `json:"cert" xml:",chardata" yaml:"Certificate" toml:"Certificate" required:"true" tlsUri:"ParamCert" validate:"required,filepath"`
} }


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