Compare commits
7 Commits
b64c318a4a
...
v1.7.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0318a9759b
|
||
|
|
1a93d5d9f3
|
||
|
|
5dc944cf21
|
||
|
|
77a85a4f84
|
||
|
|
43d1ddfeb8
|
||
|
|
db20c70d86
|
||
|
|
c0c924b75a
|
2
TODO
2
TODO
@@ -4,6 +4,8 @@
|
||||
--- https://github.com/hlandau/passlib
|
||||
-- incoprporated separately; https://git.r00t2.io/r00t2/PWGen (import r00t2.io/pwgen)
|
||||
|
||||
- auger needs to be build-constrained to linux.
|
||||
|
||||
- unit tests
|
||||
|
||||
- constants/vars for errors
|
||||
|
||||
9
auger/consts.go
Normal file
9
auger/consts.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package auger
|
||||
|
||||
const (
|
||||
augLensTpl string = "/augeas/load/%v" // A fmt.Sprintf string (single placeholder only) for Lens module roots.
|
||||
augFsTree string = "/files"
|
||||
augFsTpl string = augFsTree + "%v//%v" // A fmt.Sprintf string (first placeholder fspath, second placeholder includeDirective) for files to search for the includeDirective.
|
||||
augInclTfm string = "incl" // The transformer keyword for Augeas includes.
|
||||
augAppendSuffix string = "[last()+1]"
|
||||
)
|
||||
63
auger/funcs.go
Normal file
63
auger/funcs.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package auger
|
||||
|
||||
import (
|
||||
`io/fs`
|
||||
`os`
|
||||
`strings`
|
||||
)
|
||||
|
||||
/*
|
||||
AugpathToFspath returns the filesystem path from an Augeas path.
|
||||
|
||||
It is *required* and expected that the Augeas standard /files prefix be removed first;
|
||||
if not, it is assumed to be part of the filesystem path.
|
||||
|
||||
If a valid path cannot be determined, fsPath will be empty.
|
||||
*/
|
||||
func AugpathToFspath(augPath string) (fsPath string, err error) {
|
||||
|
||||
var path string
|
||||
var num int
|
||||
var augSplit []string = strings.Split(augPath, "/")
|
||||
|
||||
num = len(augSplit)
|
||||
|
||||
for i := num - 1; i >= 0; i-- {
|
||||
path = strings.Join(augSplit[:i], "/")
|
||||
if !fs.ValidPath(path) {
|
||||
continue
|
||||
}
|
||||
if _, err = os.Stat(path); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = nil
|
||||
continue
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fsPath = path
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// dedupePaths is used to reduce new to only unique entries that do not exist in existing.
|
||||
func dedupePaths(new, existing []string) (missing []string) {
|
||||
|
||||
var ok bool
|
||||
var m map[string]bool = make(map[string]bool)
|
||||
|
||||
for _, path := range existing {
|
||||
m[path] = true
|
||||
}
|
||||
|
||||
for _, path := range new {
|
||||
if _, ok = m[path]; !ok {
|
||||
missing = append(missing, path)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
294
auger/funcs_aug.go
Normal file
294
auger/funcs_aug.go
Normal file
@@ -0,0 +1,294 @@
|
||||
package auger
|
||||
|
||||
import (
|
||||
`bufio`
|
||||
`errors`
|
||||
`fmt`
|
||||
`io`
|
||||
`os`
|
||||
`path/filepath`
|
||||
`strings`
|
||||
|
||||
`github.com/davecgh/go-spew/spew`
|
||||
`github.com/google/shlex`
|
||||
`honnef.co/go/augeas`
|
||||
`r00t2.io/sysutils/paths`
|
||||
)
|
||||
|
||||
// Close cleanly closes the underlying Augeas connection.
|
||||
func (a *Aug) Close() {
|
||||
|
||||
a.aug.Close()
|
||||
|
||||
}
|
||||
|
||||
// Interact provides an interactive shell-like interface for debugging purposes to explore the loaded Augeas tree.
|
||||
func (a *Aug) Interact() (err error) {
|
||||
|
||||
var input string
|
||||
var lexed []string
|
||||
var cmd string
|
||||
var arg string
|
||||
var val string
|
||||
var augVal string
|
||||
var augVals []string
|
||||
var buf *bufio.Reader = bufio.NewReader(os.Stdin)
|
||||
|
||||
fmt.Fprint(os.Stderr, "INTERACTIVE MODE\nCmds: get, getall, match, set, quit\n")
|
||||
breakCmd:
|
||||
for {
|
||||
cmd, arg, val = "", "", ""
|
||||
|
||||
fmt.Fprint(os.Stderr, "> ")
|
||||
if input, err = buf.ReadString('\n'); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
err = nil
|
||||
break breakCmd
|
||||
}
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(input) == "" {
|
||||
continue
|
||||
}
|
||||
if lexed, err = shlex.Split(input); err != nil {
|
||||
return
|
||||
}
|
||||
if lexed == nil || len(lexed) == 0 || len(lexed) > 3 {
|
||||
fmt.Fprintf(os.Stderr, "Bad command: %#v\n", lexed)
|
||||
continue
|
||||
}
|
||||
cmd = lexed[0]
|
||||
switch len(lexed) {
|
||||
case 2:
|
||||
arg = lexed[1]
|
||||
case 3:
|
||||
arg = lexed[1]
|
||||
val = lexed[2]
|
||||
}
|
||||
switch cmd {
|
||||
case "get":
|
||||
if strings.TrimSpace(arg) == "" {
|
||||
fmt.Fprintln(os.Stderr, "Missing argument")
|
||||
continue
|
||||
}
|
||||
if augVal, err = a.aug.Get(arg); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "!! ERROR !!\n%v\n", spew.Sdump(err))
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, augVal)
|
||||
case "getall":
|
||||
if strings.TrimSpace(arg) == "" {
|
||||
fmt.Fprintln(os.Stderr, "Missing argument")
|
||||
continue
|
||||
}
|
||||
if augVals, err = a.aug.GetAll(arg); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "!! ERROR !!\n%v\n", spew.Sdump(err))
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, strings.Join(augVals, "\n"))
|
||||
case "match":
|
||||
if strings.TrimSpace(arg) == "" {
|
||||
fmt.Fprintln(os.Stderr, "Missing argument")
|
||||
continue
|
||||
}
|
||||
if augVals, err = a.aug.Match(arg); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "!! ERROR !!\n%v\n", spew.Sdump(err))
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, strings.Join(augVals, "\n"))
|
||||
case "set":
|
||||
if strings.TrimSpace(arg) == "" {
|
||||
fmt.Fprintln(os.Stderr, "Missing argument")
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(val) == "" {
|
||||
fmt.Fprintln(os.Stderr, "Missing value")
|
||||
continue
|
||||
}
|
||||
if err = a.aug.Set(arg, val); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "!! ERROR !!\n%v\n", spew.Sdump(err))
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, "Success!")
|
||||
case "quit":
|
||||
break breakCmd
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown command: %v\n", cmd)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
RecursiveInclude parses the configuration files belonging to Augeas lens name augLens,
|
||||
searching for all occurrences of includeDirective, loading those files (if they exist),
|
||||
and continuing so forth recursively, loading them into the Augeas file tree.
|
||||
|
||||
If any relative paths are found, they will be assumed to be relative to fsRoot ("/" if empty).
|
||||
For e.g. Nginx, you almost absolutely want to set this to "/etc/nginx", but you really should
|
||||
use absolute paths for every include in your configs if supported by the application; it will
|
||||
lead to much less guesswork and much more accurate recursing/walking.
|
||||
|
||||
Some lens recursively load depending on their respective include directive(s) automatically;
|
||||
some (such as the Nginx lens) do not.
|
||||
|
||||
For example for Nginx, augLens should be "Nginx". RecursiveInclude will then iterate over
|
||||
/augeas/load/Nginx/incl (/augeas/load/<augLens>/incl), parsing each file for includeDirective
|
||||
(the "include" keyword, in Nginx's case), check if it is already loaded in /augeas/load/<augLens>/incl,
|
||||
adding it and reloading if not, and then scanning *that* file for includeDirective, etc.
|
||||
|
||||
An error will be returned if augLens is a nonexistent or not-loaded Augeas lens module.
|
||||
|
||||
Depending on how many files there are and whether globs vs. explicit filepaths are included, this may take a while.
|
||||
*/
|
||||
func (a *Aug) RecursiveInclude(augLens, includeDirective, fsRoot string) (err error) {
|
||||
|
||||
if err = a.addIncl(includeDirective, augLens, fsRoot, nil); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
addIncl is used by RecursiveInclude.
|
||||
|
||||
includeDirective, augLens, and fsRoot have the same meaning as in RecursiveInclude.
|
||||
|
||||
newInclPaths are new filesystem paths/Augeas-compatible glob patterns to load into the filetree and recurse into.
|
||||
They may be nil, especially if the first run.
|
||||
*/
|
||||
func (a *Aug) addIncl(includeDirective, augLens string, fsRoot string, newInclPaths []string) (err error) {
|
||||
|
||||
var matches []string // Passed around set of Augeas matches.
|
||||
var includes []string // Filepath(s)/glob(s) from fetching includeDirective in lensInclPath. These are internal to the application but are recursed.
|
||||
var lensInclPath string // The path of the included paths in the tree. These are internal to Augeas, not the application.
|
||||
var appendPath string // The path for new Augeas includes.
|
||||
var match []string // A placeholder for iterating when populating includes.
|
||||
var fpath string // A placeholder for finding the path of a conf file that contains an includeDirective.
|
||||
var lensPath string = fmt.Sprintf(augLensTpl, augLens) // The path of the lens (augLens) itself.
|
||||
var augErr *augeas.Error = new(augeas.Error) // We use this to skip "nonexistent" lens.
|
||||
|
||||
if fsRoot == "" {
|
||||
fsRoot = "/"
|
||||
}
|
||||
if err = paths.RealPath(&fsRoot); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for strings.HasSuffix(lensPath, "/") {
|
||||
lensPath = lensPath[:len(lensPath)-1]
|
||||
}
|
||||
if !strings.HasSuffix(lensPath, "/"+augInclTfm) {
|
||||
lensPath = strings.TrimSuffix(lensPath, "/"+augInclTfm)
|
||||
}
|
||||
lensInclPath = fmt.Sprintf("%v/%v", lensPath, augInclTfm)
|
||||
appendPath = fmt.Sprintf("%v/%v", lensInclPath, augAppendSuffix)
|
||||
|
||||
// First canonize paths.
|
||||
if newInclPaths != nil && len(newInclPaths) > 0 {
|
||||
// Existing includes. We don't return on an empty lensInclPath because
|
||||
if matches, err = a.aug.Match(lensInclPath); err != nil {
|
||||
if errors.As(err, augErr) && augErr.Code == augeas.NoMatch {
|
||||
err = nil
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
for idx, m := range matches {
|
||||
if matches[idx], err = a.aug.Get(m); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize new include(s).
|
||||
for idx, i := range newInclPaths {
|
||||
if !filepath.IsAbs(i) {
|
||||
newInclPaths[idx] = filepath.Join(fsRoot, i)
|
||||
}
|
||||
if err = paths.RealPath(&newInclPaths[idx]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// We don't want to bother adding multiple incl's for the same path(s); it can negatively affect Augeas loads.
|
||||
newInclPaths = dedupePaths(newInclPaths, matches)
|
||||
|
||||
// Add the new path(s) as Augeas include entries.
|
||||
if newInclPaths != nil {
|
||||
for _, fsPath := range newInclPaths {
|
||||
if err = a.aug.Set(appendPath, fsPath); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
/*
|
||||
And then load the new files into the tree.
|
||||
This is done at the end as it takes WAYYY less time to just reload the tree
|
||||
as a whole once you have more than, say, 30 files added at a time.
|
||||
*/
|
||||
if err = a.aug.Load(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We now fetch all values (filepath/globs) that match the includeDirective and recurse with those as new include files.
|
||||
matches = nil
|
||||
if includes, err = a.aug.GetAll(lensInclPath); err != nil {
|
||||
return
|
||||
}
|
||||
for _, fsPath := range includes {
|
||||
// This gets the Augeas filetree path, NOT the FS path...
|
||||
if match, err = a.aug.Match(fmt.Sprintf(augFsTpl, fsPath, includeDirective)); err != nil {
|
||||
return
|
||||
}
|
||||
if match == nil || len(match) == 0 {
|
||||
continue
|
||||
}
|
||||
/*
|
||||
For each directive match, we need to:
|
||||
|
||||
1.) normalize to an FS *file* path (strip augFsTree from the beginning
|
||||
2.) walk backwards (via AugpathToFspath) until we find an actual file
|
||||
3.) get the *dirname* of that file
|
||||
4.) join the value (included file) to #3
|
||||
IF
|
||||
fsRoot == "/"
|
||||
|
||||
This very obviously breaks for applications with arbitrary roots (like Nginx including relative to /etc/nginx).
|
||||
That's why we warn about it in Aug.RecursiveInclude. Caveat emptor.
|
||||
*/
|
||||
for idx, ftreePath := range match {
|
||||
if fpath, err = a.aug.Get(ftreePath); err != nil {
|
||||
return
|
||||
}
|
||||
if fsRoot == "/" {
|
||||
if ftreePath, err = AugpathToFspath(
|
||||
strings.TrimSuffix(ftreePath, augFsTree),
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
if ftreePath != "" {
|
||||
fpath = filepath.Join(filepath.Dir(ftreePath), fpath)
|
||||
}
|
||||
}
|
||||
match[idx] = fpath
|
||||
}
|
||||
matches = append(matches, match...)
|
||||
}
|
||||
|
||||
if matches != nil && len(matches) != 0 {
|
||||
if err = a.addIncl(includeDirective, augLens, fsRoot, matches); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
41
auger/funcs_augflags.go
Normal file
41
auger/funcs_augflags.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package auger
|
||||
|
||||
import (
|
||||
`honnef.co/go/augeas`
|
||||
)
|
||||
|
||||
// Eval returns an evaluated set of flags.
|
||||
func (a *AugFlags) Eval() (augFlags augeas.Flag) {
|
||||
|
||||
augFlags = augeas.None
|
||||
|
||||
if a.Backup != nil && *a.Backup {
|
||||
augFlags |= augeas.SaveBackup
|
||||
}
|
||||
if a.NewFile != nil && *a.NewFile {
|
||||
augFlags |= augeas.SaveNewFile
|
||||
}
|
||||
if a.TypeCheck != nil && *a.TypeCheck {
|
||||
augFlags |= augeas.TypeCheck
|
||||
}
|
||||
if a.NoDfltModLoad != nil && *a.NoDfltModLoad {
|
||||
augFlags |= augeas.NoModlAutoload
|
||||
}
|
||||
if a.DryRun != nil && *a.DryRun {
|
||||
augFlags |= augeas.SaveNoop
|
||||
}
|
||||
if a.NoTree != nil && *a.NoTree {
|
||||
augFlags |= augeas.NoLoad
|
||||
}
|
||||
if a.NoAutoModLoad != nil && *a.NoAutoModLoad {
|
||||
augFlags |= augeas.NoModlAutoload
|
||||
}
|
||||
if a.EnableSpan != nil && *a.EnableSpan {
|
||||
augFlags |= augeas.EnableSpan
|
||||
}
|
||||
if a.NoErrClose != nil && *a.NoErrClose {
|
||||
augFlags |= augeas.NoErrClose
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
62
auger/types.go
Normal file
62
auger/types.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package auger
|
||||
|
||||
import (
|
||||
`honnef.co/go/augeas`
|
||||
)
|
||||
|
||||
// Aug is a wrapper around (honnef.co/go/)augeas.Augeas. Remember to call Aug.Close().
|
||||
type Aug struct {
|
||||
aug augeas.Augeas
|
||||
}
|
||||
|
||||
// AugFlags contains flags to pass to the Augeas instance.
|
||||
type AugFlags struct {
|
||||
/*
|
||||
Backup, if true, will enable Augeas backup mode (original files are saved with a .augsave suffix).
|
||||
const: augeas.SaveBackup
|
||||
*/
|
||||
Backup *bool `toml:"Backup,omitempty"`
|
||||
/*
|
||||
NewFile, if true, will create new files (.augnew suffix) instead of overwriting the original file.
|
||||
const: augeas.SaveNewFile
|
||||
*/
|
||||
NewFile *bool `toml:"NewFile,omitempty"`
|
||||
/*
|
||||
TypeCheck, if true, will perform a Lens typecheck.
|
||||
HIGHLY UNRECOMMENDED; WILL INDUCE A HUGE FRONTLOAD.
|
||||
const: augeas.TypeCheck
|
||||
*/
|
||||
TypeCheck *bool `toml:"TypeCheck,omitempty"`
|
||||
/*
|
||||
NoDfltModLoad, if true, will suppress loading the built-in/default modules.
|
||||
Highly unrecommended, as we do not have a current way in the config to define load paths (yet).
|
||||
const: augeas.NoStdinc
|
||||
*/
|
||||
NoDfltModLoad *bool `toml:"NoDfltModLoad,omitempty"`
|
||||
/*
|
||||
DryRun, if true, will make all saves NO-OPs.
|
||||
const: augeas.SaveNoop
|
||||
*/
|
||||
DryRun *bool `toml:"DryRun,omitempty"`
|
||||
/*
|
||||
NoTree, if true, will not load the filetree automatically. Doesn't really affect this program.
|
||||
const: augeas.NoLoad
|
||||
*/
|
||||
NoTree *bool `toml:"NoTree,omitempty"`
|
||||
/*
|
||||
NoAutoModLoad, if true, will supress automatically loading modules.
|
||||
const: augeas.NoModlAutoload
|
||||
*/
|
||||
NoAutoModLoad *bool `toml:"NoAutoModLoad,omitempty"`
|
||||
/*
|
||||
EnableSpan, if true, will track span in input nodes (location information, essentially).
|
||||
See https://augeas.net/docs/api.html#getting-the-span-of-a-node-related-to-a-file
|
||||
const: augeas.EnableSpan
|
||||
*/
|
||||
EnableSpan *bool `toml:"EnableSpan,omitempty"`
|
||||
/*
|
||||
NoErrClose, if true, will suppress automatically closing on error.
|
||||
const: augeas.NoErrClose
|
||||
*/
|
||||
NoErrClose *bool `toml:"NoErrClose,omitempty"`
|
||||
}
|
||||
3
cryptparse/TODO
Normal file
3
cryptparse/TODO
Normal file
@@ -0,0 +1,3 @@
|
||||
- PKCS#12/PFX parsing/support
|
||||
|
||||
- Move to struct tags and reflection, so it can not only be easier to maintain in the future but also be implemented in custom structs downstream.
|
||||
134
cryptparse/consts.go
Normal file
134
cryptparse/consts.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package cryptparse
|
||||
|
||||
import (
|
||||
`crypto/tls`
|
||||
|
||||
`github.com/go-playground/validator/v10`
|
||||
)
|
||||
|
||||
var (
|
||||
tlsVerNmToUint map[string]uint16
|
||||
tlsCipherNmToUint map[string]uint16
|
||||
tlsCurveNmToCurve map[string]tls.CurveID
|
||||
)
|
||||
|
||||
const (
|
||||
MaxTlsCipher uint16 = tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
|
||||
MaxCurveId tls.CurveID = tls.X25519 // 29
|
||||
MinTlsVer uint16 = tls.VersionSSL30
|
||||
MaxTlsVer uint16 = tls.VersionTLS13
|
||||
DefaultNetType string = "tcp"
|
||||
)
|
||||
|
||||
// TlsUriParam* specifiy URL query parameters to parse a tls:// URI, and are used by TlsUri methods.
|
||||
const (
|
||||
/*
|
||||
TlsUriParamCa specifies a path to a CA certificate PEM-encded DER file.
|
||||
|
||||
It may be specified multiple times in a TLS URI.
|
||||
*/
|
||||
TlsUriParamCa string = "pki_ca"
|
||||
/*
|
||||
TlsUriParamCert specifies a path to a client certificate PEM-encded DER file.
|
||||
|
||||
It may be specified multiple times in a TLS URI.
|
||||
*/
|
||||
TlsUriParamCert string = "pki_cert"
|
||||
/*
|
||||
TlsUriParamKey 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, 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.
|
||||
Note that TLS 1.3 has a fixed set of ciphers, and
|
||||
this list may not be respected by the remote end.
|
||||
|
||||
The string may either be the name (as per
|
||||
https://www.iana.org/assignments/tls-parameters/tls-parameters.xml)
|
||||
or an int (normal, hex, etc. string representation).
|
||||
|
||||
It may be specified multiple times in a TLS URI.
|
||||
*/
|
||||
TlsUriParamCipher string = "cipher"
|
||||
/*
|
||||
TlsUriParamCurve specifies one (or more) curve(s)
|
||||
to specify for the TLS connection cipher negotiation.
|
||||
|
||||
It may be specified multiple times in a TLS URI.
|
||||
*/
|
||||
TlsUriParamCurve string = "curve"
|
||||
/*
|
||||
TlsUriParamMinTls defines the minimum version of the
|
||||
TLS protocol to use.
|
||||
It is recommended to use "TLS_1.3".
|
||||
|
||||
Supported syntax formats include:
|
||||
|
||||
* TLS_1.3
|
||||
* 1.3
|
||||
* v1.3
|
||||
* TLSv1.3
|
||||
* 0x0304 (legacy_version, see RFC8446 § 4.1.2)
|
||||
* 774 (0x0304 in int form)
|
||||
* 0o1404 (0x0304 in octal form)
|
||||
|
||||
All evaluate to TLS 1.3 in this example.
|
||||
|
||||
Only the first defined instance is parsed.
|
||||
*/
|
||||
TlsUriParamMinTls string = "min_tls"
|
||||
/*
|
||||
TlsUriParamMaxTls defines the minimum version of the
|
||||
TLS protocol to use.
|
||||
|
||||
See TlsUriParamMinTls for syntax of the value.
|
||||
|
||||
Only the first defined instance is parsed.
|
||||
*/
|
||||
TlsUriParamMaxTls string = "max_tls"
|
||||
/*
|
||||
TlsUriParamNet is used by TlsUri.ToConn and TlsUri.ToTlsConn to explicitly specify a network.
|
||||
|
||||
The default is "tcp".
|
||||
|
||||
See net.Dial()'s "network" parameter for valid network types.
|
||||
|
||||
Only the first defined instance is parsed.
|
||||
*/
|
||||
TlsUriParamNet string = "net"
|
||||
)
|
||||
|
||||
var (
|
||||
paramBoolValsTrue []string = []string{
|
||||
"1", "yes", "y", "true",
|
||||
}
|
||||
paramBoolValsFalse []string = []string{
|
||||
"0", "no", "n", "false",
|
||||
}
|
||||
validate *validator.Validate = validator.New(validator.WithRequiredStructEnabled())
|
||||
)
|
||||
13
cryptparse/errs.go
Normal file
13
cryptparse/errs.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package cryptparse
|
||||
|
||||
import (
|
||||
`errors`
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBadTlsCipher error = errors.New("invalid TLS cipher suite")
|
||||
ErrBadTlsCurve error = errors.New("invalid TLS curve")
|
||||
ErrBadTlsVer error = errors.New("invalid TLS version")
|
||||
ErrUnknownCipher error = errors.New("unknown TLS cipher")
|
||||
ErrUnknownKey error = errors.New("unknown key type")
|
||||
)
|
||||
826
cryptparse/funcs.go
Normal file
826
cryptparse/funcs.go
Normal file
@@ -0,0 +1,826 @@
|
||||
package cryptparse
|
||||
|
||||
import (
|
||||
`bytes`
|
||||
`crypto`
|
||||
`crypto/ecdh`
|
||||
`crypto/ecdsa`
|
||||
`crypto/ed25519`
|
||||
`crypto/rsa`
|
||||
`crypto/tls`
|
||||
`crypto/x509`
|
||||
`encoding/pem`
|
||||
`errors`
|
||||
`net/url`
|
||||
`os`
|
||||
`strconv`
|
||||
`strings`
|
||||
|
||||
`r00t2.io/sysutils/paths`
|
||||
)
|
||||
|
||||
// FromURL returns a *TlsUri from a *url.URL.
|
||||
func FromURL(u *url.URL) (t *TlsUri) {
|
||||
|
||||
var newU *url.URL
|
||||
|
||||
if u == nil {
|
||||
return
|
||||
}
|
||||
|
||||
newU = new(url.URL)
|
||||
*newU = *u
|
||||
if u.User != nil {
|
||||
newU.User = new(url.Userinfo)
|
||||
*newU.User = *u.User
|
||||
}
|
||||
|
||||
newU.Scheme = "tls"
|
||||
|
||||
t = &TlsUri{
|
||||
URL: newU,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// IsMatchedPair returns true if the privateKey is paired with the cert.
|
||||
func IsMatchedPair(privKey crypto.PrivateKey, cert *x509.Certificate) (isMatched bool, err error) {
|
||||
|
||||
var pubkey crypto.PublicKey
|
||||
|
||||
if cert == nil || privKey == nil {
|
||||
return
|
||||
}
|
||||
|
||||
pubkey = cert.PublicKey
|
||||
|
||||
switch k := privKey.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
if p, ok := pubkey.(*rsa.PublicKey); ok {
|
||||
isMatched = k.PublicKey.Equal(p)
|
||||
return
|
||||
}
|
||||
case ed25519.PrivateKey:
|
||||
if p, ok := pubkey.(ed25519.PublicKey); ok {
|
||||
// Order is flipped here because unlike the other key types, an ed25519.PrivateKey is just a []byte.
|
||||
isMatched = p.Equal(k.Public())
|
||||
return
|
||||
}
|
||||
case *ecdh.PrivateKey:
|
||||
if p, ok := pubkey.(*ecdh.PublicKey); ok {
|
||||
isMatched = k.PublicKey().Equal(p)
|
||||
return
|
||||
}
|
||||
case *ecdsa.PrivateKey:
|
||||
if p, ok := pubkey.(*ecdsa.PublicKey); ok {
|
||||
isMatched = k.PublicKey.Equal(p)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If we got here, we can't determine either the private key type or the cert's public key type.
|
||||
err = ErrUnknownKey
|
||||
|
||||
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.
|
||||
|
||||
The string may either be the name (as per https://www.iana.org/assignments/tls-parameters/tls-parameters.xml)
|
||||
or an int (normal, hex, etc. string representation).
|
||||
|
||||
If none is found, the default is MaxTlsCipher.
|
||||
*/
|
||||
func ParseTlsCipher(s string) (cipherSuite uint16, err error) {
|
||||
|
||||
var nm string
|
||||
var n uint64
|
||||
var i uint16
|
||||
var ok bool
|
||||
|
||||
if n, err = strconv.ParseUint(s, 10, 16); err != nil {
|
||||
if errors.Is(err, strconv.ErrSyntax) {
|
||||
// It's a name; parse below.
|
||||
err = nil
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// It's a number.
|
||||
if nm = tls.CipherSuiteName(uint16(n)); strings.HasPrefix(nm, "0x") {
|
||||
// ...but invalid.
|
||||
err = ErrBadTlsCipher
|
||||
return
|
||||
} else {
|
||||
// Valid (as number). Return it.
|
||||
cipherSuite = uint16(n)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
s = strings.ToUpper(s)
|
||||
s = strings.ReplaceAll(s, " ", "_")
|
||||
|
||||
// We build a dynamic map of cipher suite names to uint16s (if not already created).
|
||||
if tlsCipherNmToUint == nil {
|
||||
tlsCipherNmToUint = make(map[string]uint16)
|
||||
for i = 0; i <= MaxTlsCipher; i++ {
|
||||
if nm = tls.VersionName(i); !strings.HasPrefix(nm, "0x") {
|
||||
tlsCipherNmToUint[nm] = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cipherSuite = MaxTlsCipher
|
||||
if i, ok = tlsCipherNmToUint[s]; ok {
|
||||
cipherSuite = i
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ParseTlsCipherStrict is like ParseTlsCipher, but an ErrUnknownCipher error will be raised if no matching cipher is found.
|
||||
func ParseTlsCipherStrict(s string) (cipherSuite uint16, err error) {
|
||||
|
||||
var nm string
|
||||
var n uint64
|
||||
var i uint16
|
||||
var ok bool
|
||||
|
||||
if n, err = strconv.ParseUint(s, 10, 16); err != nil {
|
||||
if errors.Is(err, strconv.ErrSyntax) {
|
||||
// It's a name; parse below.
|
||||
err = nil
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// It's a number.
|
||||
if nm = tls.CipherSuiteName(uint16(n)); strings.HasPrefix(nm, "0x") {
|
||||
// ...but invalid.
|
||||
err = ErrBadTlsCipher
|
||||
return
|
||||
} else {
|
||||
// Valid (as number). Return it.
|
||||
cipherSuite = uint16(n)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
s = strings.ToUpper(s)
|
||||
s = strings.ReplaceAll(s, " ", "_")
|
||||
|
||||
// We build a dynamic map of cipher suite names to uint16s (if not already created).
|
||||
if tlsCipherNmToUint == nil {
|
||||
tlsCipherNmToUint = make(map[string]uint16)
|
||||
for i = 0; i <= MaxTlsCipher; i++ {
|
||||
if nm = tls.VersionName(i); !strings.HasPrefix(nm, "0x") {
|
||||
tlsCipherNmToUint[nm] = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if i, ok = tlsCipherNmToUint[s]; ok {
|
||||
cipherSuite = i
|
||||
} else {
|
||||
err = ErrUnknownCipher
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
ParseTlsCiphers parses s as a comma-separated list of cipher suite names/integers and returns a slice of suites.
|
||||
|
||||
See ParseTlsCipher for details, as this is mostly just a wrapper around it.
|
||||
|
||||
If no cipher suites are found, cipherSuites will only contain MaxTlsCipher.
|
||||
*/
|
||||
func ParseTlsCiphers(s string) (cipherSuites []uint16) {
|
||||
|
||||
var suiteNms []string
|
||||
var cipher uint16
|
||||
var err error
|
||||
|
||||
suiteNms = strings.Split(s, ",")
|
||||
cipherSuites = make([]uint16, 0, len(suiteNms))
|
||||
|
||||
for _, nm := range suiteNms {
|
||||
if cipher, err = ParseTlsCipher(nm); err != nil {
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
cipherSuites = append(cipherSuites, cipher)
|
||||
}
|
||||
|
||||
if len(cipherSuites) == 0 {
|
||||
cipherSuites = []uint16{tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ParseTlsCipherSuite is like ParseTlsCipher but returns a *tls.CipherSuite instead of a uint16 TLS cipher identifier.
|
||||
func ParseTlsCipherSuite(s string) (cipherSuite *tls.CipherSuite, err error) {
|
||||
|
||||
var cipherId uint16
|
||||
|
||||
if cipherId, err = ParseTlsCipher(s); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, v := range tls.CipherSuites() {
|
||||
if v.ID == cipherId {
|
||||
cipherSuite = v
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, v := range tls.InsecureCipherSuites() {
|
||||
if v.ID == cipherId {
|
||||
cipherSuite = v
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ParseTlsCipherSuiteStrict is like ParseTlsCipherSuite, but an ErrUnknownCipher error will be raised if no matching cipher is found.
|
||||
func ParseTlsCipherSuiteStrict(s string) (cipherSuite *tls.CipherSuite, err error) {
|
||||
|
||||
var cipherId uint16
|
||||
|
||||
if cipherId, err = ParseTlsCipherStrict(s); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, v := range tls.CipherSuites() {
|
||||
if v.ID == cipherId {
|
||||
cipherSuite = v
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, v := range tls.InsecureCipherSuites() {
|
||||
if v.ID == cipherId {
|
||||
cipherSuite = v
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ParseTlsCipherSuites is like ParseTlsCiphers but returns a []*tls.CipherSuite instead of a []uint16 of TLS cipher identifiers.
|
||||
func ParseTlsCipherSuites(s string) (cipherSuites []*tls.CipherSuite, err error) {
|
||||
|
||||
var found bool
|
||||
var cipherIds []uint16
|
||||
|
||||
cipherIds = ParseTlsCiphers(s)
|
||||
|
||||
for _, cipherId := range cipherIds {
|
||||
found = false
|
||||
for _, v := range tls.CipherSuites() {
|
||||
if v.ID == cipherId {
|
||||
cipherSuites = append(cipherSuites, v)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
for _, v := range tls.InsecureCipherSuites() {
|
||||
if v.ID == cipherId {
|
||||
cipherSuites = append(cipherSuites, v)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
ParseTlsCurve parses string s and attempts to derive a tls.CurveID from it.
|
||||
|
||||
The string may either be the name (as per // https://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-8)
|
||||
or an int (normal, hex, etc. string representation).
|
||||
*/
|
||||
func ParseTlsCurve(s string) (curve tls.CurveID, err error) {
|
||||
|
||||
var i tls.CurveID
|
||||
var n uint64
|
||||
var ok bool
|
||||
|
||||
if n, err = strconv.ParseUint(s, 10, 16); err != nil {
|
||||
if errors.Is(err, strconv.ErrSyntax) {
|
||||
// It's a name; parse below.
|
||||
err = nil
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// It's a number.
|
||||
if strings.HasPrefix(tls.CurveID(uint16(n)).String(), "CurveID(") {
|
||||
// ...but invalid.
|
||||
err = ErrBadTlsCurve
|
||||
return
|
||||
} else {
|
||||
// Valid (as number). Return it.
|
||||
curve = tls.CurveID(uint16(n))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// It seems to be a name. Normalize...
|
||||
s = strings.ToUpper(s)
|
||||
|
||||
// Unfortunately there's no "tls.CurveIDName()" function.
|
||||
// They do have a .String() method though.
|
||||
if tlsCurveNmToCurve == nil {
|
||||
tlsCurveNmToCurve = make(map[string]tls.CurveID)
|
||||
for i = 0; i <= MaxCurveId; i++ {
|
||||
if strings.HasPrefix(i.String(), "CurveID(") {
|
||||
continue
|
||||
}
|
||||
tlsCurveNmToCurve[i.String()] = i
|
||||
// It's normally mixed-case; we want to be able to look it up in a normalized all-caps as well.
|
||||
tlsCurveNmToCurve[strings.ToUpper(i.String())] = i
|
||||
// The normal name, except for X25519, has "Curve" in the front. We add it without that prefix as well.
|
||||
tlsCurveNmToCurve[strings.TrimPrefix(i.String(), "Curve")] = i
|
||||
}
|
||||
}
|
||||
|
||||
curve = MaxCurveId
|
||||
if _, ok = tlsCurveNmToCurve[s]; ok {
|
||||
curve = tlsCurveNmToCurve[s]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
ParseTlsCurves parses s as a comma-separated list of tls.CurveID names/integers and returns a slice of tls.CurveID.
|
||||
|
||||
See ParseTlsCurve for details, as this is mostly just a wrapper around it.
|
||||
|
||||
If no curves are found, curves will only contain MaxCurveId.
|
||||
*/
|
||||
func ParseTlsCurves(s string) (curves []tls.CurveID) {
|
||||
|
||||
var curveNms []string
|
||||
var curve tls.CurveID
|
||||
var err error
|
||||
|
||||
curveNms = strings.Split(s, ",")
|
||||
curves = make([]tls.CurveID, 0, len(curveNms))
|
||||
|
||||
for _, nm := range curveNms {
|
||||
if curve, err = ParseTlsCurve(nm); err != nil {
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
curves = append(curves, curve)
|
||||
}
|
||||
|
||||
if len(curves) == 0 {
|
||||
curves = []tls.CurveID{MaxCurveId}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
ParseTlsUri parses a "TLS URI"'s query parameters. All certs and keys must be in PEM format.
|
||||
|
||||
You probably don't need this and should instead be using TlsUri.ToTlsConfig.
|
||||
It just wraps this, but is probably more convenient.
|
||||
*/
|
||||
func ParseTlsUri(tlsUri *url.URL) (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 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 buf *bytes.Buffer = new(bytes.Buffer)
|
||||
var srvNm string = tlsUri.Hostname()
|
||||
|
||||
params = tlsUri.Query()
|
||||
|
||||
if params == nil {
|
||||
tlsConf = &tls.Config{
|
||||
ServerName: srvNm,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// These are all filepath(s).
|
||||
for _, k := range []string{
|
||||
TlsUriParamCa,
|
||||
TlsUriParamCert,
|
||||
TlsUriParamKey,
|
||||
} {
|
||||
if _, ok = params[k]; ok {
|
||||
for idx, _ := range params[k] {
|
||||
if err = paths.RealPath(¶ms[k][idx]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CA cert(s).
|
||||
buf.Reset()
|
||||
if _, ok = params[TlsUriParamCa]; ok {
|
||||
rootCAs = x509.NewCertPool()
|
||||
for _, c := range params[TlsUriParamCa] {
|
||||
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. These are done first so we can match to a client certificate.
|
||||
buf.Reset()
|
||||
if _, ok = params[TlsUriParamKey]; ok {
|
||||
for _, k := range params[TlsUriParamKey] {
|
||||
if b, err = os.ReadFile(k); err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
err = nil
|
||||
continue
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
buf.Write(b)
|
||||
}
|
||||
if privKeys, err = ParsePrivateKey(buf.Bytes()); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// (Client) Certificate(s).
|
||||
buf.Reset()
|
||||
if _, ok = params[TlsUriParamCert]; ok {
|
||||
for _, c := range params[TlsUriParamCert] {
|
||||
if b, err = os.ReadFile(c); err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
err = nil
|
||||
continue
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
buf.Write(b)
|
||||
}
|
||||
if tlsCerts, err = ParseLeafCert(buf.Bytes(), privKeys, intermediateCAs...); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Hostname (Override).
|
||||
if _, ok = params[TlsUriParamSni]; ok {
|
||||
srvNm = params[TlsUriParamSni][0]
|
||||
}
|
||||
|
||||
// Disable Verification.
|
||||
if _, ok = params[TlsUriParamNoVerify]; ok {
|
||||
val = strings.ToLower(params[TlsUriParamNoVerify][0])
|
||||
for _, i := range paramBoolValsTrue {
|
||||
if i == val {
|
||||
allowInvalid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ciphers.
|
||||
if _, ok = params[TlsUriParamCipher]; ok {
|
||||
ciphers = ParseTlsCiphers(strings.Join(params[TlsUriParamCipher], ","))
|
||||
}
|
||||
|
||||
// Minimum TLS Protocol Version.
|
||||
if _, ok = params[TlsUriParamMinTls]; ok {
|
||||
if minVer, err = ParseTlsVersion(params[TlsUriParamMinTls][0]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Maximum TLS Protocol Version.
|
||||
if _, ok = params[TlsUriParamMaxTls]; ok {
|
||||
if maxVer, err = ParseTlsVersion(params[TlsUriParamMaxTls][0]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Curves.
|
||||
if _, ok = params[TlsUriParamCurve]; ok {
|
||||
curves = ParseTlsCurves(strings.Join(params[TlsUriParamCurve], ","))
|
||||
}
|
||||
|
||||
tlsConf = &tls.Config{
|
||||
Certificates: tlsCerts,
|
||||
RootCAs: rootCAs,
|
||||
ServerName: srvNm,
|
||||
InsecureSkipVerify: allowInvalid,
|
||||
CipherSuites: ciphers,
|
||||
MinVersion: minVer,
|
||||
MaxVersion: maxVer,
|
||||
CurvePreferences: curves,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ParseTlsVersion parses string s and attempts to derive a TLS version from it. If none is found, tlsVer will be 0.
|
||||
func ParseTlsVersion(s string) (tlsVer uint16, err error) {
|
||||
|
||||
var nm string
|
||||
var n uint64
|
||||
var i uint16
|
||||
var ok bool
|
||||
|
||||
if n, err = strconv.ParseUint(s, 10, 16); err != nil {
|
||||
if errors.Is(err, strconv.ErrSyntax) {
|
||||
// It's a name; parse below.
|
||||
err = nil
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// It's a number.
|
||||
if nm = tls.VersionName(uint16(n)); strings.HasPrefix(nm, "0x") {
|
||||
// ...but invalid.
|
||||
err = ErrBadTlsVer
|
||||
return
|
||||
} else {
|
||||
// Valid (as number). Return it.
|
||||
tlsVer = uint16(n)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, it should be parsed as a version string.
|
||||
s = strings.ToUpper(s)
|
||||
s = strings.ReplaceAll(s, "_", " ")
|
||||
s = strings.ReplaceAll(s, "V", " ")
|
||||
s = strings.TrimSpace(s)
|
||||
if !strings.HasPrefix(s, "SSL") && !strings.HasPrefix(s, "TLS ") {
|
||||
s = "TLS " + s
|
||||
}
|
||||
|
||||
// We build a dynamic map of version names to uint16s (if not already created).
|
||||
if tlsVerNmToUint == nil {
|
||||
tlsVerNmToUint = make(map[string]uint16)
|
||||
for i = MinTlsVer; i <= MaxTlsVer; i++ {
|
||||
if nm = tls.VersionName(i); !strings.HasPrefix(nm, "0x") {
|
||||
tlsVerNmToUint[nm] = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if i, ok = tlsVerNmToUint[s]; ok {
|
||||
tlsVer = i
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
ParseCA parses PEM bytes and returns an *x509.CertPool in caCerts.
|
||||
|
||||
Concatenated PEM files are supported.
|
||||
|
||||
Any keys found will be filtered out, as will any leaf certificates.
|
||||
|
||||
Any *intermediate* CAs (the certificate is a CA but it is not self-signed) will be returned separate from
|
||||
certPool.
|
||||
|
||||
Ordering from the file is preserved in the returned slices.
|
||||
*/
|
||||
func ParseCA(certRaw []byte) (certPool *x509.CertPool, rootCerts []*x509.Certificate, intermediateCerts []*x509.Certificate, err error) {
|
||||
|
||||
var pemBlocks []*pem.Block
|
||||
var cert *x509.Certificate
|
||||
var certs []*x509.Certificate
|
||||
|
||||
if pemBlocks, err = SplitPem(certRaw); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Filter out keys etc. and non-CA certs.
|
||||
for _, b := range pemBlocks {
|
||||
if b.Type != "CERTIFICATE" {
|
||||
continue
|
||||
}
|
||||
if cert, err = x509.ParseCertificate(b.Bytes); err != nil {
|
||||
return
|
||||
}
|
||||
if !cert.IsCA {
|
||||
continue
|
||||
}
|
||||
certs = append(certs, cert)
|
||||
}
|
||||
|
||||
for _, cert = range certs {
|
||||
if bytes.Equal(cert.RawIssuer, cert.RawSubject) {
|
||||
// It's a root/self-signed.
|
||||
rootCerts = append(rootCerts, cert)
|
||||
} else {
|
||||
// It's an intermediate.
|
||||
intermediateCerts = append(intermediateCerts, cert)
|
||||
}
|
||||
}
|
||||
|
||||
if rootCerts != nil {
|
||||
certPool = x509.NewCertPool()
|
||||
for _, cert = range rootCerts {
|
||||
certPool.AddCert(cert)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
ParseLeafCert parses PEM bytes from a (client) certificate file, iterates over a slice of
|
||||
crypto.PrivateKey (finding one that matches), and returns one (or more) tls.Certificate.
|
||||
|
||||
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
|
||||
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
|
||||
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 *root* CAs found will be discarded. 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.
|
||||
Any certs without a corresponding key will be discarded.
|
||||
*/
|
||||
func ParseLeafCert(certRaw []byte, keys []crypto.PrivateKey, intermediates ...*x509.Certificate) (tlsCerts []tls.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 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, b := range pemBlocks {
|
||||
if strings.Contains(b.Type, "PRIVATE KEY") {
|
||||
parsedKeysBuf.Write(pem.EncodeToMemory(b))
|
||||
continue
|
||||
}
|
||||
if b.Type != "CERTIFICATE" {
|
||||
continue
|
||||
}
|
||||
if cert, err = x509.ParseCertificate(b.Bytes); err != nil {
|
||||
return
|
||||
}
|
||||
if cert.IsCA {
|
||||
if bytes.Equal(cert.RawIssuer, cert.RawSubject) {
|
||||
caCerts = append(caCerts, cert)
|
||||
} else {
|
||||
intermediates = append(intermediates, cert)
|
||||
}
|
||||
}
|
||||
certs = append(certs, cert)
|
||||
}
|
||||
|
||||
if intermediates != nil && len(intermediates) != 0 {
|
||||
interBytes = make([][]byte, len(intermediates))
|
||||
for _, i := range intermediates {
|
||||
interBytes = append(interBytes, i.Raw)
|
||||
}
|
||||
}
|
||||
|
||||
if parsedKeysBuf.Len() != 0 {
|
||||
if parsedKeys, err = ParsePrivateKey(parsedKeysBuf.Bytes()); err != nil {
|
||||
return
|
||||
}
|
||||
keys = append(keys, parsedKeys...)
|
||||
}
|
||||
|
||||
// Now pair the certs and keys, and combine as a tls.Certificate.
|
||||
for _, cert = range certs {
|
||||
foundKey = nil
|
||||
for _, k := range keys {
|
||||
if isMatched, err = IsMatchedPair(k, cert); err != nil {
|
||||
return
|
||||
}
|
||||
if isMatched {
|
||||
foundKey = k
|
||||
break
|
||||
}
|
||||
}
|
||||
if foundKey == nil && !skipKeyPair {
|
||||
continue
|
||||
}
|
||||
tlsCerts = append(
|
||||
tlsCerts,
|
||||
tls.Certificate{
|
||||
Certificate: append([][]byte{cert.Raw}, interBytes...),
|
||||
PrivateKey: foundKey,
|
||||
Leaf: cert,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
_ = caCerts
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
ParsePrivateKey parses PEM bytes to a private key. Multiple keys may be concatenated in the same file.
|
||||
|
||||
Any public keys, certificates, etc. found will be discarded.
|
||||
*/
|
||||
func ParsePrivateKey(keyRaw []byte) (keys []crypto.PrivateKey, err error) {
|
||||
|
||||
var privKey crypto.PrivateKey
|
||||
var pemBlocks []*pem.Block
|
||||
|
||||
if pemBlocks, err = SplitPem(keyRaw); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, b := range pemBlocks {
|
||||
if !strings.Contains(b.Type, "PRIVATE KEY") {
|
||||
continue
|
||||
}
|
||||
switch b.Type {
|
||||
case "RSA PRIVATE KEY": // PKCS#1
|
||||
if privKey, err = x509.ParsePKCS1PrivateKey(b.Bytes); err != nil {
|
||||
return
|
||||
}
|
||||
keys = append(keys, privKey)
|
||||
case "EC PRIVATE KEY": // SEC 1, ASN.1 DER
|
||||
if privKey, err = x509.ParseECPrivateKey(b.Bytes); err != nil {
|
||||
return
|
||||
}
|
||||
keys = append(keys, privKey)
|
||||
case "PRIVATE KEY": // PKCS#8
|
||||
if privKey, err = x509.ParsePKCS8PrivateKey(b.Bytes); err != nil {
|
||||
return
|
||||
}
|
||||
keys = append(keys, privKey)
|
||||
default:
|
||||
err = ErrUnknownKey
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SplitPem splits a single block of bytes into one (or more) (encoding/)pem.Blocks.
|
||||
func SplitPem(pemRaw []byte) (blocks []*pem.Block, err error) {
|
||||
|
||||
var block *pem.Block
|
||||
var rest []byte
|
||||
|
||||
for block, rest = pem.Decode(pemRaw); block != nil; block, rest = pem.Decode(rest) {
|
||||
blocks = append(blocks, block)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
217
cryptparse/funcs_tlsflat.go
Normal file
217
cryptparse/funcs_tlsflat.go
Normal file
@@ -0,0 +1,217 @@
|
||||
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 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, err = ParseLeafCert(b, privKeys, intermediateCAs...); err != nil {
|
||||
return
|
||||
}
|
||||
tlsCerts = append(tlsCerts, parsedTlsCerts...)
|
||||
}
|
||||
}
|
||||
|
||||
// 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(TlsUriParamCa, c)
|
||||
}
|
||||
}
|
||||
|
||||
// Keys and Certs.
|
||||
if t.Certs != nil {
|
||||
for _, c := range t.Certs {
|
||||
u.Query().Add(TlsUriParamCert, c.CertFile)
|
||||
if c.KeyFile != nil {
|
||||
u.Query().Add(TlsUriParamKey, *c.KeyFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce the SNI hostname.
|
||||
u.Query().Add(TlsUriParamSni, t.SniName)
|
||||
|
||||
// Disable Verification.
|
||||
if t.SkipVerify {
|
||||
u.Query().Add(TlsUriParamNoVerify, "1")
|
||||
}
|
||||
|
||||
// Ciphers.
|
||||
if t.CipherSuites != nil {
|
||||
for _, c := range t.CipherSuites {
|
||||
u.Query().Add(TlsUriParamCipher, c)
|
||||
}
|
||||
}
|
||||
|
||||
// Minimum TLS Protocol Version.
|
||||
if t.MinTlsProtocol != nil {
|
||||
u.Query().Add(TlsUriParamMinTls, *t.MinTlsProtocol)
|
||||
}
|
||||
|
||||
// Maximum TLS Protocol Version.
|
||||
if t.MaxTlsProtocol != nil {
|
||||
u.Query().Add(TlsUriParamMaxTls, *t.MaxTlsProtocol)
|
||||
}
|
||||
|
||||
// Curves.
|
||||
if t.Curves != nil {
|
||||
for _, c := range t.Curves {
|
||||
u.Query().Add(TlsUriParamCurve, c)
|
||||
}
|
||||
}
|
||||
|
||||
tlsUri = &TlsUri{
|
||||
URL: u,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
256
cryptparse/funcs_tlsuri.go
Normal file
256
cryptparse/funcs_tlsuri.go
Normal file
@@ -0,0 +1,256 @@
|
||||
package cryptparse
|
||||
|
||||
import (
|
||||
`crypto`
|
||||
`crypto/tls`
|
||||
`net`
|
||||
`net/url`
|
||||
`os`
|
||||
`strings`
|
||||
)
|
||||
|
||||
/*
|
||||
WithConn returns a (crypto/)tls.Conn from an existing/already dialed net.Conn.
|
||||
|
||||
underlying should be a "bare" net.Conn; behavior is undefined/unknown if the underlying conn is already a (crypto/)tls.Conn.
|
||||
*/
|
||||
func (t *TlsUri) WithConn(underlying net.Conn) (conn *tls.Conn, err error) {
|
||||
|
||||
var cfg *tls.Config
|
||||
|
||||
if cfg, err = t.ToTlsConfig(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
conn = tls.Client(underlying, cfg)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
ToConn returns a "bare" net.Conn (already dialed) from a TlsUri.
|
||||
|
||||
Note that this does NOT include the TLS configured or initialized; use TlsUri.ToTlsConn for that.
|
||||
(A (crypto/)tls.Conn conforms to net.Conn.)
|
||||
|
||||
An error will be returned if no port is explicitly defined in the TlsUri.
|
||||
*/
|
||||
func (t *TlsUri) ToConn() (conn net.Conn, err error) {
|
||||
|
||||
var ok bool
|
||||
var connHost string
|
||||
var params map[string][]string
|
||||
var netType string = DefaultNetType
|
||||
|
||||
params = t.Query()
|
||||
|
||||
if params != nil {
|
||||
if _, ok = params[TlsUriParamNet]; ok {
|
||||
netType = params[TlsUriParamNet][0]
|
||||
}
|
||||
}
|
||||
netType = strings.ToLower(netType)
|
||||
|
||||
switch netType {
|
||||
case "unix", "unixgram", "unixpacket":
|
||||
connHost = t.Path
|
||||
default:
|
||||
connHost = t.Host
|
||||
}
|
||||
|
||||
if conn, err = net.Dial(netType, connHost); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
ToTlsConfig returns a *tls.Config from a TlsUri.
|
||||
|
||||
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 *TlsUri) ToTlsConfig() (cfg *tls.Config, err error) {
|
||||
|
||||
if cfg, err = ParseTlsUri(t.URL); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
ToTlsConn returns a (crypto/)tls.Conn (already dialed) from a TlsUri.
|
||||
|
||||
An error will be returned if no port is explicitly defined in the TlsUri.
|
||||
*/
|
||||
func (t *TlsUri) ToTlsConn() (conn *tls.Conn, err error) {
|
||||
|
||||
var ok bool
|
||||
var cfg *tls.Config
|
||||
var connHost string
|
||||
var params map[string][]string
|
||||
var netType string = DefaultNetType
|
||||
|
||||
if cfg, err = t.ToTlsConfig(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
params = t.Query()
|
||||
|
||||
if params != nil {
|
||||
if _, ok = params[TlsUriParamNet]; ok {
|
||||
netType = params[TlsUriParamNet][0]
|
||||
}
|
||||
}
|
||||
netType = strings.ToLower(netType)
|
||||
|
||||
switch netType {
|
||||
case "unix", "unixgram", "unixpacket":
|
||||
connHost = t.Path
|
||||
default:
|
||||
connHost = t.Host
|
||||
}
|
||||
|
||||
if conn, err = tls.Dial(netType, connHost, cfg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ToTlsFlat returns a *TlsFlat from a TlsUri.
|
||||
func (t *TlsUri) ToTlsFlat() (tlsFlat *TlsFlat, err error) {
|
||||
|
||||
var b []byte
|
||||
var params url.Values
|
||||
var paramMap map[string][]string
|
||||
// These also have maps so they can backmap filenames.
|
||||
var privKeys []crypto.PrivateKey
|
||||
var privKeyMap map[string][]crypto.PrivateKey
|
||||
var tlsCerts []tls.Certificate
|
||||
var tlsCertMap map[string][]tls.Certificate
|
||||
var isMatch bool
|
||||
var fCert *TlsFlatCert
|
||||
var val string
|
||||
var f TlsFlat = TlsFlat{
|
||||
SniName: t.Hostname(),
|
||||
SkipVerify: false,
|
||||
Certs: nil,
|
||||
CaFiles: nil,
|
||||
CipherSuites: nil,
|
||||
MinTlsProtocol: nil,
|
||||
MaxTlsProtocol: nil,
|
||||
Curves: nil,
|
||||
}
|
||||
|
||||
params = t.Query()
|
||||
paramMap = params
|
||||
|
||||
if params == nil {
|
||||
tlsFlat = &f
|
||||
return
|
||||
}
|
||||
|
||||
// CA cert(s).
|
||||
if t.Query().Has(TlsUriParamCa) {
|
||||
f.CaFiles = append(f.CaFiles, paramMap[TlsUriParamCa]...)
|
||||
}
|
||||
|
||||
// Keys and Certs. These are done first so we can match to a client certificate.
|
||||
if t.Query().Has(TlsUriParamKey) {
|
||||
privKeyMap = make(map[string][]crypto.PrivateKey)
|
||||
for _, kFile := range paramMap[TlsUriParamKey] {
|
||||
if b, err = os.ReadFile(kFile); err != nil {
|
||||
return
|
||||
}
|
||||
if privKeyMap[kFile], err = ParsePrivateKey(b); err != nil {
|
||||
return
|
||||
}
|
||||
privKeys = append(privKeys, privKeyMap[kFile]...)
|
||||
}
|
||||
}
|
||||
if t.Query().Has(TlsUriParamCert) {
|
||||
tlsCertMap = make(map[string][]tls.Certificate)
|
||||
for _, cFile := range paramMap[TlsUriParamCert] {
|
||||
if b, err = os.ReadFile(cFile); err != nil {
|
||||
return
|
||||
}
|
||||
if tlsCertMap[cFile], err = ParseLeafCert(b, privKeys); err != nil {
|
||||
return
|
||||
}
|
||||
tlsCerts = append(tlsCerts, tlsCertMap[cFile]...)
|
||||
}
|
||||
}
|
||||
// We then correlate. Whew, lads.
|
||||
for cFile, c := range tlsCertMap {
|
||||
for _, cert := range c {
|
||||
for kFile, k := range privKeyMap {
|
||||
if isMatch, err = IsMatchedPair(k, cert.Leaf); err != nil {
|
||||
return
|
||||
} else if isMatch {
|
||||
fCert = &TlsFlatCert{
|
||||
CertFile: cFile,
|
||||
KeyFile: new(string),
|
||||
}
|
||||
*fCert.KeyFile = kFile
|
||||
f.Certs = append(f.Certs, fCert)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hostname.
|
||||
if t.Query().Has(TlsUriParamSni) {
|
||||
f.SniName = t.Query().Get(TlsUriParamSni)
|
||||
}
|
||||
|
||||
// Disable verification.
|
||||
if t.Query().Has(TlsUriParamNoVerify) {
|
||||
val = strings.ToLower(t.Query().Get(TlsUriParamNoVerify))
|
||||
for _, i := range paramBoolValsTrue {
|
||||
if val == i {
|
||||
f.SkipVerify = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ciphers.
|
||||
if t.Query().Has(TlsUriParamCipher) {
|
||||
f.CipherSuites = params[TlsUriParamCipher]
|
||||
}
|
||||
|
||||
// Minimum TLS Protocol Version.
|
||||
if t.Query().Has(TlsUriParamMinTls) {
|
||||
f.MinTlsProtocol = new(string)
|
||||
*f.MinTlsProtocol = t.Query().Get(TlsUriParamMinTls)
|
||||
}
|
||||
|
||||
// Maximum TLS Protocol Version.
|
||||
if t.Query().Has(TlsUriParamMaxTls) {
|
||||
f.MaxTlsProtocol = new(string)
|
||||
*f.MaxTlsProtocol = t.Query().Get(TlsUriParamMaxTls)
|
||||
}
|
||||
|
||||
// Curves.
|
||||
if t.Query().Has(TlsUriParamCurve) {
|
||||
f.Curves = params[TlsUriParamCurve]
|
||||
}
|
||||
|
||||
tlsFlat = &f
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ToURL returns the *url.URL representation of a TlsUri. Note that the params will remain, so remove them explicitly if needed.
|
||||
func (t *TlsUri) ToURL() (u *url.URL) {
|
||||
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
u = t.URL
|
||||
|
||||
return
|
||||
}
|
||||
30
cryptparse/types.go
Normal file
30
cryptparse/types.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package cryptparse
|
||||
|
||||
import (
|
||||
`encoding/xml`
|
||||
`net/url`
|
||||
)
|
||||
|
||||
// TlsFlat provides an easy structure to marshal/unmarshal a tls.Config from/to a data structure (JSON, XML, etc.).
|
||||
type TlsFlat struct {
|
||||
XMLName xml.Name `xml:"tlsConfig" json:"-" yaml:"-" toml:"-"`
|
||||
SniName string `json:"sni_name" xml:"sniName,attr" yaml:"SniName" toml:"SniName" required:"true" validate:"required"`
|
||||
SkipVerify bool `json:"skip_verify,omitempty" xml:"skipVerify,attr,omitempty" yaml:"SkipVerify,omitempty" toml:"SkipVerify,omitempty"`
|
||||
Certs []*TlsFlatCert `json:"certs,omitempty" xml:"certs>cert,omitempty" yaml:"Certs,omitempty" toml:"Certs,omitempty" validate:"omitempty,dive"`
|
||||
CaFiles []string `json:"ca_files,omitempty" xml:"roots>ca,omitempty" yaml:"CaFiles,omitempty" toml:"CaFiles,omitempty" validate:"omitempty,dive,filepath"`
|
||||
CipherSuites []string `json:"cipher_suites,omitempty" xml:"ciphers,omitempty" yaml:"CipherSuites,omitempty" toml:"CipherSuites,omitempty"`
|
||||
MinTlsProtocol *string `json:"min_tls_protocol,omitempty" xml:"minTlsProtocol,attr,omitempty" yaml:"MinTlsProtocol,omitempty" toml:"MinTlsProtocol,omitempty"`
|
||||
MaxTlsProtocol *string `json:"max_tls_protocol,omitempty" xml:"maxTlsProtocol,attr,omitempty" yaml:"MaxTlsProtocol,omitempty" toml:"MaxTlsProtocol,omitempty"`
|
||||
Curves []string `json:"curves,omitempty" xml:"curves>curve,omitempty" yaml:"Curves,omitempty" toml:"Curves,omitempty" validate:"omitempty,dive"`
|
||||
}
|
||||
|
||||
// TlsFlatCert represents a certificate (and, possibly, paired key).
|
||||
type TlsFlatCert struct {
|
||||
XMLName xml.Name `xml:"cert" json:"-" yaml:"-" toml:"-"`
|
||||
KeyFile *string `json:"key,omitempty" xml:"key,attr,omitempty" yaml:"Key,omitempty" toml:"Key,omitempty" validate:"omitempty,filepath"`
|
||||
CertFile string `json:"cert" xml:",chardata" yaml:"Certificate" toml:"Certificate" required:"true" validate:"required,filepath"`
|
||||
}
|
||||
|
||||
type TlsUri struct {
|
||||
*url.URL
|
||||
}
|
||||
@@ -17,6 +17,34 @@ import (
|
||||
`r00t2.io/sysutils/paths`
|
||||
)
|
||||
|
||||
/*
|
||||
DefEnv operates like Python's .get() method on dicts (maps);
|
||||
if the environment variable specified by key does not exist/is not specified,
|
||||
then the value specified by fallback will be returned instead
|
||||
otherwise key's value is returned.
|
||||
*/
|
||||
func DefEnv(key, fallback string) (value string) {
|
||||
|
||||
var exists bool
|
||||
|
||||
if value, exists = os.LookupEnv(key); !exists {
|
||||
value = fallback
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DefEnvBlank is like DefEnv but will ADDITIONALLY/ALSO apply fallback if key is *defined/exists but is an empty string*.
|
||||
func DefEnvBlank(key, fallback string) (value string) {
|
||||
|
||||
value = DefEnv(key, fallback)
|
||||
if value == "" {
|
||||
value = fallback
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetEnvMap returns a map of all environment variables. All values are strings.
|
||||
func GetEnvMap() (envVars map[string]string) {
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
package envs
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
|
||||
13
go.mod
13
go.mod
@@ -5,14 +5,21 @@ go 1.21
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/g0rbe/go-chattr v1.0.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/go-playground/validator/v10 v10.22.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
golang.org/x/sys v0.19.0
|
||||
honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8
|
||||
r00t2.io/goutils v1.6.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect
|
||||
github.com/godbus/dbus v4.1.0+incompatible // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // 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.19.0 // indirect
|
||||
golang.org/x/net v0.21.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
)
|
||||
|
||||
// Pending https://github.com/g0rbe/go-chattr/pull/3
|
||||
|
||||
38
go.sum
38
go.sum
@@ -1,22 +1,40 @@
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
|
||||
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/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
||||
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
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.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
|
||||
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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=
|
||||
github.com/johnnybubonic/go-chattr v0.0.0-20240126141003-459f46177b13 h1:tgEbuE4bNVjaCWWIB1u9lDzGqH/ZdBTg33+4vNW2rjg=
|
||||
github.com/johnnybubonic/go-chattr v0.0.0-20240126141003-459f46177b13/go.mod h1:yQc6VPJfpDDC1g+W2t47+yYmzBNioax/GLiyJ25/IOs=
|
||||
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/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.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
r00t2.io/goutils v1.4.0 h1:/x/etLpMFv3+j1aPtT7KK2G0uOk+gQkGvXIYBCdjn3E=
|
||||
r00t2.io/goutils v1.4.0/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
|
||||
r00t2.io/goutils v1.5.0 h1:haVk+wUK1BAk8f4UFGjy3ov3DwGMauZAOv/XYdb9isQ=
|
||||
r00t2.io/goutils v1.5.0/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8 h1:FW42yWB1sGClqswyHIB68wo0+oPrav1IuQ+Tdy8Qp8E=
|
||||
honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8/go.mod h1:44w9OfBSQ9l3o59rc2w3AnABtE44bmtNnRMNC7z+oKE=
|
||||
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=
|
||||
|
||||
Reference in New Issue
Block a user