Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8348c5fd65 | |||
| 1eafb638a8 | |||
| 3ad562b6fa | |||
| 0f77bd49dc | |||
| f31d74243f |
@@ -64,4 +64,4 @@ workflows:
|
|||||||
testbuild:
|
testbuild:
|
||||||
jobs:
|
jobs:
|
||||||
- testbuild
|
- testbuild
|
||||||
# - lint
|
- lint
|
||||||
|
|||||||
@@ -18,6 +18,19 @@ issues:
|
|||||||
# Default: 3
|
# Default: 3
|
||||||
max-same-issues: 50
|
max-same-issues: 50
|
||||||
|
|
||||||
|
# Exclude some lints for CLI programs under cmd/ (package main).
|
||||||
|
# The project allows fmt.Print* in command-line tools; keep forbidigo for libraries.
|
||||||
|
exclude-rules:
|
||||||
|
- path: ^cmd/
|
||||||
|
linters:
|
||||||
|
- forbidigo
|
||||||
|
- path: cmd/.*
|
||||||
|
linters:
|
||||||
|
- forbidigo
|
||||||
|
- path: .*/cmd/.*
|
||||||
|
linters:
|
||||||
|
- forbidigo
|
||||||
|
|
||||||
formatters:
|
formatters:
|
||||||
enable:
|
enable:
|
||||||
- goimports # checks if the code and import statements are formatted according to the 'goimports' command
|
- goimports # checks if the code and import statements are formatted according to the 'goimports' command
|
||||||
@@ -73,7 +86,6 @@ linters:
|
|||||||
- godoclint # checks Golang's documentation practice
|
- godoclint # checks Golang's documentation practice
|
||||||
- godot # checks if comments end in a period
|
- godot # checks if comments end in a period
|
||||||
- gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod
|
- gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod
|
||||||
- goprintffuncname # checks that printf-like functions are named with f at the end
|
|
||||||
- gosec # inspects source code for security problems
|
- gosec # inspects source code for security problems
|
||||||
- govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
|
- govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
|
||||||
- iface # checks the incorrect use of interfaces, helping developers avoid interface pollution
|
- iface # checks the incorrect use of interfaces, helping developers avoid interface pollution
|
||||||
@@ -230,6 +242,10 @@ linters:
|
|||||||
check-type-assertions: true
|
check-type-assertions: true
|
||||||
exclude-functions:
|
exclude-functions:
|
||||||
- (*git.wntrmute.dev/kyle/goutils/sbuf.Buffer).Write
|
- (*git.wntrmute.dev/kyle/goutils/sbuf.Buffer).Write
|
||||||
|
- git.wntrmute.dev/kyle/goutils/lib.Warn
|
||||||
|
- git.wntrmute.dev/kyle/goutils/lib.Warnx
|
||||||
|
- git.wntrmute.dev/kyle/goutils/lib.Err
|
||||||
|
- git.wntrmute.dev/kyle/goutils/lib.Errx
|
||||||
|
|
||||||
exhaustive:
|
exhaustive:
|
||||||
# Program elements to check for exhaustiveness.
|
# Program elements to check for exhaustiveness.
|
||||||
@@ -321,6 +337,12 @@ linters:
|
|||||||
# https://github.com/godoc-lint/godoc-lint?tab=readme-ov-file#no-unused-link
|
# https://github.com/godoc-lint/godoc-lint?tab=readme-ov-file#no-unused-link
|
||||||
- no-unused-link
|
- no-unused-link
|
||||||
|
|
||||||
|
gosec:
|
||||||
|
excludes:
|
||||||
|
- G104 # handled by errcheck
|
||||||
|
- G301
|
||||||
|
- G306
|
||||||
|
|
||||||
govet:
|
govet:
|
||||||
# Enable all analyzers.
|
# Enable all analyzers.
|
||||||
# Default: false
|
# Default: false
|
||||||
@@ -356,6 +378,12 @@ linters:
|
|||||||
- os.WriteFile
|
- os.WriteFile
|
||||||
- prometheus.ExponentialBuckets.*
|
- prometheus.ExponentialBuckets.*
|
||||||
- prometheus.LinearBuckets
|
- prometheus.LinearBuckets
|
||||||
|
ignored-numbers:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- 3
|
||||||
|
- 4
|
||||||
|
- 8
|
||||||
|
|
||||||
nakedret:
|
nakedret:
|
||||||
# Make an issue if func has more lines of code than this setting, and it has naked returns.
|
# Make an issue if func has more lines of code than this setting, and it has naked returns.
|
||||||
@@ -424,6 +452,8 @@ linters:
|
|||||||
# Omit embedded fields from selector expression.
|
# Omit embedded fields from selector expression.
|
||||||
# https://staticcheck.dev/docs/checks/#QF1008
|
# https://staticcheck.dev/docs/checks/#QF1008
|
||||||
- -QF1008
|
- -QF1008
|
||||||
|
# We often explicitly enable old/deprecated ciphers for research.
|
||||||
|
- -SA1019
|
||||||
|
|
||||||
usetesting:
|
usetesting:
|
||||||
# Enable/disable `os.TempDir()` detections.
|
# Enable/disable `os.TempDir()` detections.
|
||||||
@@ -452,6 +482,8 @@ linters:
|
|||||||
linters: [ testableexamples ]
|
linters: [ testableexamples ]
|
||||||
- path: 'main.go'
|
- path: 'main.go'
|
||||||
linters: [ forbidigo, mnd, reassign ]
|
linters: [ forbidigo, mnd, reassign ]
|
||||||
|
- path: 'cmd/cruntar/main.go'
|
||||||
|
linters: [ unparam ]
|
||||||
- source: 'TODO'
|
- source: 'TODO'
|
||||||
linters: [ godot ]
|
linters: [ godot ]
|
||||||
- text: 'should have a package comment'
|
- text: 'should have a package comment'
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
|
|
||||||
|
v1.11.1 - 2025-11-16
|
||||||
|
|
||||||
|
Changed
|
||||||
|
- cmd: complete linting fixes across programs; no functional changes.
|
||||||
|
|
||||||
v1.11.0 - 2025-11-15
|
v1.11.0 - 2025-11-15
|
||||||
|
|
||||||
Added
|
Added
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ func TestReset(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const decay = 5 * time.Millisecond
|
const decay = 25 * time.Millisecond
|
||||||
const maxDuration = 10 * time.Millisecond
|
const maxDuration = 10 * time.Millisecond
|
||||||
const interval = time.Millisecond
|
const interval = time.Millisecond
|
||||||
|
|
||||||
|
|||||||
126
cache/lru/lru_internal_test.go
vendored
126
cache/lru/lru_internal_test.go
vendored
@@ -1,87 +1,87 @@
|
|||||||
package lru
|
package lru
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/benbjohnson/clock"
|
"github.com/benbjohnson/clock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// These tests mirror the MRU-style behavior present in this LRU package
|
// These tests mirror the MRU-style behavior present in this LRU package
|
||||||
// implementation (eviction removes the most-recently-used entry).
|
// implementation (eviction removes the most-recently-used entry).
|
||||||
func TestBasicCacheEviction(t *testing.T) {
|
func TestBasicCacheEviction(t *testing.T) {
|
||||||
mock := clock.NewMock()
|
mock := clock.NewMock()
|
||||||
c := NewStringKeyCache[int](2)
|
c := NewStringKeyCache[int](2)
|
||||||
c.clock = mock
|
c.clock = mock
|
||||||
|
|
||||||
if err := c.ConsistencyCheck(); err != nil {
|
if err := c.ConsistencyCheck(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Len() != 0 {
|
if c.Len() != 0 {
|
||||||
t.Fatal("cache should have size 0")
|
t.Fatal("cache should have size 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.evict()
|
c.evict()
|
||||||
if err := c.ConsistencyCheck(); err != nil {
|
if err := c.ConsistencyCheck(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Store("raven", 1)
|
c.Store("raven", 1)
|
||||||
if err := c.ConsistencyCheck(); err != nil {
|
if err := c.ConsistencyCheck(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.store) != 1 {
|
if len(c.store) != 1 {
|
||||||
t.Fatalf("store should have length=1, have length=%d", len(c.store))
|
t.Fatalf("store should have length=1, have length=%d", len(c.store))
|
||||||
}
|
}
|
||||||
|
|
||||||
mock.Add(time.Second)
|
mock.Add(time.Second)
|
||||||
c.Store("owl", 2)
|
c.Store("owl", 2)
|
||||||
if err := c.ConsistencyCheck(); err != nil {
|
if err := c.ConsistencyCheck(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.store) != 2 {
|
if len(c.store) != 2 {
|
||||||
t.Fatalf("store should have length=2, have length=%d", len(c.store))
|
t.Fatalf("store should have length=2, have length=%d", len(c.store))
|
||||||
}
|
}
|
||||||
|
|
||||||
mock.Add(time.Second)
|
mock.Add(time.Second)
|
||||||
c.Store("goat", 3)
|
c.Store("goat", 3)
|
||||||
if err := c.ConsistencyCheck(); err != nil {
|
if err := c.ConsistencyCheck(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.store) != 2 {
|
if len(c.store) != 2 {
|
||||||
t.Fatalf("store should have length=2, have length=%d", len(c.store))
|
t.Fatalf("store should have length=2, have length=%d", len(c.store))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since this implementation evicts the most-recently-used item, inserting
|
// Since this implementation evicts the most-recently-used item, inserting
|
||||||
// "goat" when full evicts "owl" (the most recent at that time).
|
// "goat" when full evicts "owl" (the most recent at that time).
|
||||||
mock.Add(time.Second)
|
mock.Add(time.Second)
|
||||||
if _, ok := c.Get("owl"); ok {
|
if _, ok := c.Get("owl"); ok {
|
||||||
t.Fatal("store should not have an entry for owl (MRU-evicted)")
|
t.Fatal("store should not have an entry for owl (MRU-evicted)")
|
||||||
}
|
}
|
||||||
if err := c.ConsistencyCheck(); err != nil {
|
if err := c.ConsistencyCheck(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mock.Add(time.Second)
|
mock.Add(time.Second)
|
||||||
c.Store("elk", 4)
|
c.Store("elk", 4)
|
||||||
if err := c.ConsistencyCheck(); err != nil {
|
if err := c.ConsistencyCheck(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.Has("elk") {
|
if !c.Has("elk") {
|
||||||
t.Fatal("store should contain an entry for 'elk'")
|
t.Fatal("store should contain an entry for 'elk'")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Before storing elk, keys were: raven (older), goat (newer). Evict MRU -> goat.
|
// Before storing elk, keys were: raven (older), goat (newer). Evict MRU -> goat.
|
||||||
if !c.Has("raven") {
|
if !c.Has("raven") {
|
||||||
t.Fatal("store should contain an entry for 'raven'")
|
t.Fatal("store should contain an entry for 'raven'")
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Has("goat") {
|
if c.Has("goat") {
|
||||||
t.Fatal("store should not contain an entry for 'goat'")
|
t.Fatal("store should not contain an entry for 'goat'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
64
cache/lru/timestamps_internal_test.go
vendored
64
cache/lru/timestamps_internal_test.go
vendored
@@ -1,50 +1,50 @@
|
|||||||
package lru
|
package lru
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/benbjohnson/clock"
|
"github.com/benbjohnson/clock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// These tests validate timestamps ordering semantics for the LRU package.
|
// These tests validate timestamps ordering semantics for the LRU package.
|
||||||
// Note: The LRU timestamps are sorted with most-recent-first (descending by t).
|
// Note: The LRU timestamps are sorted with most-recent-first (descending by t).
|
||||||
func TestTimestamps(t *testing.T) {
|
func TestTimestamps(t *testing.T) {
|
||||||
ts := newTimestamps[string](3)
|
ts := newTimestamps[string](3)
|
||||||
mock := clock.NewMock()
|
mock := clock.NewMock()
|
||||||
|
|
||||||
// raven
|
// raven
|
||||||
ts.Update("raven", mock.Now().UnixNano())
|
ts.Update("raven", mock.Now().UnixNano())
|
||||||
|
|
||||||
// raven, owl
|
// raven, owl
|
||||||
mock.Add(time.Millisecond)
|
mock.Add(time.Millisecond)
|
||||||
ts.Update("owl", mock.Now().UnixNano())
|
ts.Update("owl", mock.Now().UnixNano())
|
||||||
|
|
||||||
// raven, owl, goat
|
// raven, owl, goat
|
||||||
mock.Add(time.Second)
|
mock.Add(time.Second)
|
||||||
ts.Update("goat", mock.Now().UnixNano())
|
ts.Update("goat", mock.Now().UnixNano())
|
||||||
|
|
||||||
if err := ts.ConsistencyCheck(); err != nil {
|
if err := ts.ConsistencyCheck(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// make owl the most recent
|
// make owl the most recent
|
||||||
mock.Add(time.Millisecond)
|
mock.Add(time.Millisecond)
|
||||||
ts.Update("owl", mock.Now().UnixNano())
|
ts.Update("owl", mock.Now().UnixNano())
|
||||||
if err := ts.ConsistencyCheck(); err != nil {
|
if err := ts.ConsistencyCheck(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// For LRU timestamps: most recent first. Expected order: owl, goat, raven.
|
// For LRU timestamps: most recent first. Expected order: owl, goat, raven.
|
||||||
if ts.K(0) != "owl" {
|
if ts.K(0) != "owl" {
|
||||||
t.Fatalf("first key should be owl, have %s", ts.K(0))
|
t.Fatalf("first key should be owl, have %s", ts.K(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
if ts.K(1) != "goat" {
|
if ts.K(1) != "goat" {
|
||||||
t.Fatalf("second key should be goat, have %s", ts.K(1))
|
t.Fatalf("second key should be goat, have %s", ts.K(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
if ts.K(2) != "raven" {
|
if ts.K(2) != "raven" {
|
||||||
t.Fatalf("third key should be raven, have %s", ts.K(2))
|
t.Fatalf("third key should be raven, have %s", ts.K(2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
28
cache/mru/mru_internal_test.go
vendored
28
cache/mru/mru_internal_test.go
vendored
@@ -8,9 +8,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestBasicCacheEviction(t *testing.T) {
|
func TestBasicCacheEviction(t *testing.T) {
|
||||||
mock := clock.NewMock()
|
mock := clock.NewMock()
|
||||||
c := NewStringKeyCache[int](2)
|
c := NewStringKeyCache[int](2)
|
||||||
c.clock = mock
|
c.clock = mock
|
||||||
|
|
||||||
if err := c.ConsistencyCheck(); err != nil {
|
if err := c.ConsistencyCheck(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -55,18 +55,18 @@ func TestBasicCacheEviction(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mock.Add(time.Second)
|
mock.Add(time.Second)
|
||||||
v, ok := c.Get("owl")
|
v, ok := c.Get("owl")
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("store should have an entry for owl")
|
t.Fatal("store should have an entry for owl")
|
||||||
}
|
}
|
||||||
if err := c.ConsistencyCheck(); err != nil {
|
if err := c.ConsistencyCheck(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
itm := v
|
itm := v
|
||||||
if err := c.ConsistencyCheck(); err != nil {
|
if err := c.ConsistencyCheck(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if itm != 2 {
|
if itm != 2 {
|
||||||
t.Fatalf("stored item should be 2, have %d", itm)
|
t.Fatalf("stored item should be 2, have %d", itm)
|
||||||
|
|||||||
4
cache/mru/timestamps_internal_test.go
vendored
4
cache/mru/timestamps_internal_test.go
vendored
@@ -8,8 +8,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestTimestamps(t *testing.T) {
|
func TestTimestamps(t *testing.T) {
|
||||||
ts := newTimestamps[string](3)
|
ts := newTimestamps[string](3)
|
||||||
mock := clock.NewMock()
|
mock := clock.NewMock()
|
||||||
|
|
||||||
// raven
|
// raven
|
||||||
ts.Update("raven", mock.Now().UnixNano())
|
ts.Update("raven", mock.Now().UnixNano())
|
||||||
|
|||||||
@@ -458,8 +458,6 @@ func GetKeyDERFromPEM(in []byte, password []byte) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
if procType, ok := keyDER.Headers["Proc-Type"]; ok && strings.Contains(procType, "ENCRYPTED") {
|
if procType, ok := keyDER.Headers["Proc-Type"]; ok && strings.Contains(procType, "ENCRYPTED") {
|
||||||
if password != nil {
|
if password != nil {
|
||||||
// nolintlint requires rationale:
|
|
||||||
//nolint:staticcheck // legacy RFC1423 PEM encryption supported for backward compatibility when caller supplies a password
|
|
||||||
return x509.DecryptPEMBlock(keyDER, password)
|
return x509.DecryptPEMBlock(keyDER, password)
|
||||||
}
|
}
|
||||||
return nil, certerr.DecodeError(certerr.ErrorSourcePrivateKey, certerr.ErrEncryptedPrivateKey)
|
return nil, certerr.DecodeError(certerr.ErrorSourcePrivateKey, certerr.ErrEncryptedPrivateKey)
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"flag"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.wntrmute.dev/kyle/goutils/certlib"
|
"git.wntrmute.dev/kyle/goutils/certlib"
|
||||||
@@ -23,6 +24,13 @@ var (
|
|||||||
verbose bool
|
verbose bool
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
strOK = "OK"
|
||||||
|
strExpired = "EXPIRED"
|
||||||
|
strRevoked = "REVOKED"
|
||||||
|
strUnknown = "UNKNOWN"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.BoolVar(&hardfail, "hardfail", false, "treat revocation check failures as fatal")
|
flag.BoolVar(&hardfail, "hardfail", false, "treat revocation check failures as fatal")
|
||||||
flag.DurationVar(&timeout, "timeout", 10*time.Second, "network timeout for OCSP/CRL fetches and TLS site connects")
|
flag.DurationVar(&timeout, "timeout", 10*time.Second, "network timeout for OCSP/CRL fetches and TLS site connects")
|
||||||
@@ -42,16 +50,16 @@ func main() {
|
|||||||
for _, target := range flag.Args() {
|
for _, target := range flag.Args() {
|
||||||
status, err := processTarget(target)
|
status, err := processTarget(target)
|
||||||
switch status {
|
switch status {
|
||||||
case "OK":
|
case strOK:
|
||||||
fmt.Printf("%s: OK\n", target)
|
fmt.Printf("%s: %s\n", target, strOK)
|
||||||
case "EXPIRED":
|
case strExpired:
|
||||||
fmt.Printf("%s: EXPIRED: %v\n", target, err)
|
fmt.Printf("%s: %s: %v\n", target, strExpired, err)
|
||||||
exitCode = 1
|
exitCode = 1
|
||||||
case "REVOKED":
|
case strRevoked:
|
||||||
fmt.Printf("%s: REVOKED\n", target)
|
fmt.Printf("%s: %s\n", target, strRevoked)
|
||||||
exitCode = 1
|
exitCode = 1
|
||||||
case "UNKNOWN":
|
case strUnknown:
|
||||||
fmt.Printf("%s: UNKNOWN: %v\n", target, err)
|
fmt.Printf("%s: %s: %v\n", target, strUnknown, err)
|
||||||
if hardfail {
|
if hardfail {
|
||||||
// In hardfail, treat unknown as failure
|
// In hardfail, treat unknown as failure
|
||||||
exitCode = 1
|
exitCode = 1
|
||||||
@@ -67,74 +75,77 @@ func processTarget(target string) (string, error) {
|
|||||||
return checkFile(target)
|
return checkFile(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not a file; treat as site
|
|
||||||
return checkSite(target)
|
return checkSite(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkFile(path string) (string, error) {
|
func checkFile(path string) (string, error) {
|
||||||
in, err := ioutil.ReadFile(path)
|
// Prefer high-level helpers from certlib to load certificates from disk
|
||||||
if err != nil {
|
if certs, err := certlib.LoadCertificates(path); err == nil && len(certs) > 0 {
|
||||||
return "UNKNOWN", err
|
// Evaluate the first certificate (leaf) by default
|
||||||
|
return evaluateCert(certs[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try PEM first; if that fails, try single DER cert
|
cert, err := certlib.LoadCertificate(path)
|
||||||
certs, err := certlib.ReadCertificates(in)
|
if err != nil || cert == nil {
|
||||||
if err != nil || len(certs) == 0 {
|
return strUnknown, err
|
||||||
cert, _, derr := certlib.ReadCertificate(in)
|
|
||||||
if derr != nil || cert == nil {
|
|
||||||
if err == nil {
|
|
||||||
err = derr
|
|
||||||
}
|
|
||||||
return "UNKNOWN", err
|
|
||||||
}
|
|
||||||
return evaluateCert(cert)
|
|
||||||
}
|
}
|
||||||
|
return evaluateCert(cert)
|
||||||
// Evaluate the first certificate (leaf) by default
|
|
||||||
return evaluateCert(certs[0])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkSite(hostport string) (string, error) {
|
func checkSite(hostport string) (string, error) {
|
||||||
// Use certlib/hosts to parse host/port (supports https URLs and host:port)
|
// Use certlib/hosts to parse host/port (supports https URLs and host:port)
|
||||||
target, err := hosts.ParseHost(hostport)
|
target, err := hosts.ParseHost(hostport)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "UNKNOWN", err
|
return strUnknown, err
|
||||||
}
|
}
|
||||||
|
|
||||||
d := &net.Dialer{Timeout: timeout}
|
d := &net.Dialer{Timeout: timeout}
|
||||||
conn, err := tls.DialWithDialer(d, "tcp", target.String(), &tls.Config{InsecureSkipVerify: true, ServerName: target.Host})
|
tcfg := &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
ServerName: target.Host,
|
||||||
|
} // #nosec G402 -- CLI tool only verifies revocation
|
||||||
|
td := &tls.Dialer{NetDialer: d, Config: tcfg}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
conn, err := td.DialContext(ctx, "tcp", target.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "UNKNOWN", err
|
return strUnknown, err
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
state := conn.ConnectionState()
|
tconn, ok := conn.(*tls.Conn)
|
||||||
|
if !ok {
|
||||||
|
return strUnknown, errors.New("connection is not TLS")
|
||||||
|
}
|
||||||
|
|
||||||
|
state := tconn.ConnectionState()
|
||||||
if len(state.PeerCertificates) == 0 {
|
if len(state.PeerCertificates) == 0 {
|
||||||
return "UNKNOWN", errors.New("no peer certificates presented")
|
return strUnknown, errors.New("no peer certificates presented")
|
||||||
}
|
}
|
||||||
return evaluateCert(state.PeerCertificates[0])
|
return evaluateCert(state.PeerCertificates[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluateCert(cert *x509.Certificate) (string, error) {
|
func evaluateCert(cert *x509.Certificate) (string, error) {
|
||||||
// Expiry check
|
// Delegate validity and revocation checks to certlib/revoke helper.
|
||||||
now := time.Now()
|
// It returns revoked=true for both revoked and expired/not-yet-valid.
|
||||||
if !now.Before(cert.NotAfter) {
|
// Map those cases back to our statuses using the returned error text.
|
||||||
return "EXPIRED", fmt.Errorf("expired at %s", cert.NotAfter)
|
|
||||||
}
|
|
||||||
if !now.After(cert.NotBefore) {
|
|
||||||
return "EXPIRED", fmt.Errorf("not valid until %s", cert.NotBefore)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Revocation check using certlib/revoke
|
|
||||||
revoked, ok, err := revoke.VerifyCertificateError(cert)
|
revoked, ok, err := revoke.VerifyCertificateError(cert)
|
||||||
if revoked {
|
if revoked {
|
||||||
// If revoked is true, ok will be true per implementation, err may describe why
|
if err != nil {
|
||||||
return "REVOKED", err
|
msg := err.Error()
|
||||||
|
if strings.Contains(msg, "expired") || strings.Contains(msg, "isn't valid until") ||
|
||||||
|
strings.Contains(msg, "not valid until") {
|
||||||
|
return strExpired, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strRevoked, err
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
// Revocation status could not be determined
|
// Revocation status could not be determined
|
||||||
return "UNKNOWN", err
|
return strUnknown, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return "OK", nil
|
return strOK, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.wntrmute.dev/kyle/goutils/die"
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
)
|
)
|
||||||
@@ -20,20 +23,26 @@ func main() {
|
|||||||
server += ":443"
|
server += ":443"
|
||||||
}
|
}
|
||||||
|
|
||||||
var chain string
|
d := &tls.Dialer{Config: &tls.Config{}} // #nosec G402
|
||||||
|
nc, err := d.DialContext(context.Background(), "tcp", server)
|
||||||
conn, err := tls.Dial("tcp", server, nil)
|
|
||||||
die.If(err)
|
die.If(err)
|
||||||
|
conn, ok := nc.(*tls.Conn)
|
||||||
|
if !ok {
|
||||||
|
die.With("invalid TLS connection (not a *tls.Conn)")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
details := conn.ConnectionState()
|
details := conn.ConnectionState()
|
||||||
|
var chain strings.Builder
|
||||||
for _, cert := range details.PeerCertificates {
|
for _, cert := range details.PeerCertificates {
|
||||||
p := pem.Block{
|
p := pem.Block{
|
||||||
Type: "CERTIFICATE",
|
Type: "CERTIFICATE",
|
||||||
Bytes: cert.Raw,
|
Bytes: cert.Raw,
|
||||||
}
|
}
|
||||||
chain += string(pem.EncodeToMemory(&p))
|
chain.Write(pem.EncodeToMemory(&p))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(chain)
|
fmt.Fprintln(os.Stdout, chain.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
|
//lint:file-ignore SA1019 allow strict compatibility for old certs
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"crypto/dsa"
|
"crypto/dsa"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
@@ -101,30 +103,30 @@ func extUsage(ext []x509.ExtKeyUsage) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func showBasicConstraints(cert *x509.Certificate) {
|
func showBasicConstraints(cert *x509.Certificate) {
|
||||||
fmt.Printf("\tBasic constraints: ")
|
fmt.Fprint(os.Stdout, "\tBasic constraints: ")
|
||||||
if cert.BasicConstraintsValid {
|
if cert.BasicConstraintsValid {
|
||||||
fmt.Printf("valid")
|
fmt.Fprint(os.Stdout, "valid")
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("invalid")
|
fmt.Fprint(os.Stdout, "invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
if cert.IsCA {
|
if cert.IsCA {
|
||||||
fmt.Printf(", is a CA certificate")
|
fmt.Fprint(os.Stdout, ", is a CA certificate")
|
||||||
if !cert.BasicConstraintsValid {
|
if !cert.BasicConstraintsValid {
|
||||||
fmt.Printf(" (basic constraint failure)")
|
fmt.Fprint(os.Stdout, " (basic constraint failure)")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("is not a CA certificate")
|
fmt.Fprint(os.Stdout, "is not a CA certificate")
|
||||||
if cert.KeyUsage&x509.KeyUsageKeyEncipherment != 0 {
|
if cert.KeyUsage&x509.KeyUsageKeyEncipherment != 0 {
|
||||||
fmt.Printf(" (key encipherment usage enabled!)")
|
fmt.Fprint(os.Stdout, " (key encipherment usage enabled!)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cert.MaxPathLen == 0 && cert.MaxPathLenZero) || (cert.MaxPathLen > 0) {
|
if (cert.MaxPathLen == 0 && cert.MaxPathLenZero) || (cert.MaxPathLen > 0) {
|
||||||
fmt.Printf(", max path length %d", cert.MaxPathLen)
|
fmt.Fprintf(os.Stdout, ", max path length %d", cert.MaxPathLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n")
|
fmt.Fprintln(os.Stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
const oneTrueDateFormat = "2006-01-02T15:04:05-0700"
|
const oneTrueDateFormat = "2006-01-02T15:04:05-0700"
|
||||||
@@ -136,39 +138,41 @@ var (
|
|||||||
|
|
||||||
func wrapPrint(text string, indent int) {
|
func wrapPrint(text string, indent int) {
|
||||||
tabs := ""
|
tabs := ""
|
||||||
for i := 0; i < indent; i++ {
|
var tabsSb140 strings.Builder
|
||||||
tabs += "\t"
|
for range indent {
|
||||||
|
tabsSb140.WriteString("\t")
|
||||||
}
|
}
|
||||||
|
tabs += tabsSb140.String()
|
||||||
|
|
||||||
fmt.Printf(tabs+"%s\n", wrap(text, indent))
|
fmt.Fprintf(os.Stdout, tabs+"%s\n", wrap(text, indent))
|
||||||
}
|
}
|
||||||
|
|
||||||
func displayCert(cert *x509.Certificate) {
|
func displayCert(cert *x509.Certificate) {
|
||||||
fmt.Println("CERTIFICATE")
|
fmt.Fprintln(os.Stdout, "CERTIFICATE")
|
||||||
if showHash {
|
if showHash {
|
||||||
fmt.Println(wrap(fmt.Sprintf("SHA256: %x", sha256.Sum256(cert.Raw)), 0))
|
fmt.Fprintln(os.Stdout, wrap(fmt.Sprintf("SHA256: %x", sha256.Sum256(cert.Raw)), 0))
|
||||||
}
|
}
|
||||||
fmt.Println(wrap("Subject: "+displayName(cert.Subject), 0))
|
fmt.Fprintln(os.Stdout, wrap("Subject: "+displayName(cert.Subject), 0))
|
||||||
fmt.Println(wrap("Issuer: "+displayName(cert.Issuer), 0))
|
fmt.Fprintln(os.Stdout, wrap("Issuer: "+displayName(cert.Issuer), 0))
|
||||||
fmt.Printf("\tSignature algorithm: %s / %s\n", sigAlgoPK(cert.SignatureAlgorithm),
|
fmt.Fprintf(os.Stdout, "\tSignature algorithm: %s / %s\n", sigAlgoPK(cert.SignatureAlgorithm),
|
||||||
sigAlgoHash(cert.SignatureAlgorithm))
|
sigAlgoHash(cert.SignatureAlgorithm))
|
||||||
fmt.Println("Details:")
|
fmt.Fprintln(os.Stdout, "Details:")
|
||||||
wrapPrint("Public key: "+certPublic(cert), 1)
|
wrapPrint("Public key: "+certPublic(cert), 1)
|
||||||
fmt.Printf("\tSerial number: %s\n", cert.SerialNumber)
|
fmt.Fprintf(os.Stdout, "\tSerial number: %s\n", cert.SerialNumber)
|
||||||
|
|
||||||
if len(cert.AuthorityKeyId) > 0 {
|
if len(cert.AuthorityKeyId) > 0 {
|
||||||
fmt.Printf("\t%s\n", wrap("AKI: "+dumpHex(cert.AuthorityKeyId), 1))
|
fmt.Fprintf(os.Stdout, "\t%s\n", wrap("AKI: "+dumpHex(cert.AuthorityKeyId), 1))
|
||||||
}
|
}
|
||||||
if len(cert.SubjectKeyId) > 0 {
|
if len(cert.SubjectKeyId) > 0 {
|
||||||
fmt.Printf("\t%s\n", wrap("SKI: "+dumpHex(cert.SubjectKeyId), 1))
|
fmt.Fprintf(os.Stdout, "\t%s\n", wrap("SKI: "+dumpHex(cert.SubjectKeyId), 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
wrapPrint("Valid from: "+cert.NotBefore.Format(dateFormat), 1)
|
wrapPrint("Valid from: "+cert.NotBefore.Format(dateFormat), 1)
|
||||||
fmt.Printf("\t until: %s\n", cert.NotAfter.Format(dateFormat))
|
fmt.Fprintf(os.Stdout, "\t until: %s\n", cert.NotAfter.Format(dateFormat))
|
||||||
fmt.Printf("\tKey usages: %s\n", keyUsages(cert.KeyUsage))
|
fmt.Fprintf(os.Stdout, "\tKey usages: %s\n", keyUsages(cert.KeyUsage))
|
||||||
|
|
||||||
if len(cert.ExtKeyUsage) > 0 {
|
if len(cert.ExtKeyUsage) > 0 {
|
||||||
fmt.Printf("\tExtended usages: %s\n", extUsage(cert.ExtKeyUsage))
|
fmt.Fprintf(os.Stdout, "\tExtended usages: %s\n", extUsage(cert.ExtKeyUsage))
|
||||||
}
|
}
|
||||||
|
|
||||||
showBasicConstraints(cert)
|
showBasicConstraints(cert)
|
||||||
@@ -221,13 +225,13 @@ func displayAllCerts(in []byte, leafOnly bool) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
certs, _, err = certlib.ParseCertificatesDER(in, "")
|
certs, _, err = certlib.ParseCertificatesDER(in, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lib.Warn(err, "failed to parse certificates")
|
_, _ = lib.Warn(err, "failed to parse certificates")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(certs) == 0 {
|
if len(certs) == 0 {
|
||||||
lib.Warnx("no certificates found")
|
_, _ = lib.Warnx("no certificates found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,29 +247,45 @@ func displayAllCerts(in []byte, leafOnly bool) {
|
|||||||
|
|
||||||
func displayAllCertsWeb(uri string, leafOnly bool) {
|
func displayAllCertsWeb(uri string, leafOnly bool) {
|
||||||
ci := getConnInfo(uri)
|
ci := getConnInfo(uri)
|
||||||
conn, err := tls.Dial("tcp", ci.Addr, permissiveConfig())
|
d := &tls.Dialer{Config: permissiveConfig()}
|
||||||
|
nc, err := d.DialContext(context.Background(), "tcp", ci.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lib.Warn(err, "couldn't connect to %s", ci.Addr)
|
_, _ = lib.Warn(err, "couldn't connect to %s", ci.Addr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, ok := nc.(*tls.Conn)
|
||||||
|
if !ok {
|
||||||
|
_, _ = lib.Warnx("invalid TLS connection (not a *tls.Conn)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
state := conn.ConnectionState()
|
state := conn.ConnectionState()
|
||||||
conn.Close()
|
if err = conn.Close(); err != nil {
|
||||||
|
_, _ = lib.Warn(err, "couldn't close TLS connection")
|
||||||
|
}
|
||||||
|
|
||||||
conn, err = tls.Dial("tcp", ci.Addr, verifyConfig(ci.Host))
|
d = &tls.Dialer{Config: verifyConfig(ci.Host)}
|
||||||
|
nc, err = d.DialContext(context.Background(), "tcp", ci.Addr)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
conn, ok = nc.(*tls.Conn)
|
||||||
|
if !ok {
|
||||||
|
_, _ = lib.Warnx("invalid TLS connection (not a *tls.Conn)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
err = conn.VerifyHostname(ci.Host)
|
err = conn.VerifyHostname(ci.Host)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
state = conn.ConnectionState()
|
state = conn.ConnectionState()
|
||||||
}
|
}
|
||||||
conn.Close()
|
conn.Close()
|
||||||
} else {
|
} else {
|
||||||
lib.Warn(err, "TLS verification error with server name %s", ci.Host)
|
_, _ = lib.Warn(err, "TLS verification error with server name %s", ci.Host)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(state.PeerCertificates) == 0 {
|
if len(state.PeerCertificates) == 0 {
|
||||||
lib.Warnx("no certificates found")
|
_, _ = lib.Warnx("no certificates found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,14 +295,14 @@ func displayAllCertsWeb(uri string, leafOnly bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(state.VerifiedChains) == 0 {
|
if len(state.VerifiedChains) == 0 {
|
||||||
lib.Warnx("no verified chains found; using peer chain")
|
_, _ = lib.Warnx("no verified chains found; using peer chain")
|
||||||
for i := range state.PeerCertificates {
|
for i := range state.PeerCertificates {
|
||||||
displayCert(state.PeerCertificates[i])
|
displayCert(state.PeerCertificates[i])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("TLS chain verified successfully.")
|
fmt.Fprintln(os.Stdout, "TLS chain verified successfully.")
|
||||||
for i := range state.VerifiedChains {
|
for i := range state.VerifiedChains {
|
||||||
fmt.Printf("--- Verified certificate chain %d ---\n", i+1)
|
fmt.Fprintf(os.Stdout, "--- Verified certificate chain %d ---%s", i+1, "\n")
|
||||||
for j := range state.VerifiedChains[i] {
|
for j := range state.VerifiedChains[i] {
|
||||||
displayCert(state.VerifiedChains[i][j])
|
displayCert(state.VerifiedChains[i][j])
|
||||||
}
|
}
|
||||||
@@ -290,6 +310,32 @@ func displayAllCertsWeb(uri string, leafOnly bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func shouldReadStdin(argc int, argv []string) bool {
|
||||||
|
if argc == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if argc == 1 && argv[0] == "-" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func readStdin(leafOnly bool) {
|
||||||
|
certs, err := io.ReadAll(os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
_, _ = lib.Warn(err, "couldn't read certificates from standard input")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is needed for getting certs from JSON/jq.
|
||||||
|
certs = bytes.TrimSpace(certs)
|
||||||
|
certs = bytes.ReplaceAll(certs, []byte(`\n`), []byte{0xa})
|
||||||
|
certs = bytes.Trim(certs, `"`)
|
||||||
|
displayAllCerts(certs, leafOnly)
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var leafOnly bool
|
var leafOnly bool
|
||||||
flag.BoolVar(&showHash, "d", false, "show hashes of raw DER contents")
|
flag.BoolVar(&showHash, "d", false, "show hashes of raw DER contents")
|
||||||
@@ -297,32 +343,23 @@ func main() {
|
|||||||
flag.BoolVar(&leafOnly, "l", false, "only show the leaf certificate")
|
flag.BoolVar(&leafOnly, "l", false, "only show the leaf certificate")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if flag.NArg() == 0 || (flag.NArg() == 1 && flag.Arg(0) == "-") {
|
if shouldReadStdin(flag.NArg(), flag.Args()) {
|
||||||
certs, err := io.ReadAll(os.Stdin)
|
readStdin(leafOnly)
|
||||||
if err != nil {
|
return
|
||||||
lib.Warn(err, "couldn't read certificates from standard input")
|
}
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is needed for getting certs from JSON/jq.
|
for _, filename := range flag.Args() {
|
||||||
certs = bytes.TrimSpace(certs)
|
fmt.Fprintf(os.Stdout, "--%s ---%s", filename, "\n")
|
||||||
certs = bytes.Replace(certs, []byte(`\n`), []byte{0xa}, -1)
|
if strings.HasPrefix(filename, "https://") {
|
||||||
certs = bytes.Trim(certs, `"`)
|
displayAllCertsWeb(filename, leafOnly)
|
||||||
displayAllCerts(certs, leafOnly)
|
} else {
|
||||||
} else {
|
in, err := os.ReadFile(filename)
|
||||||
for _, filename := range flag.Args() {
|
if err != nil {
|
||||||
fmt.Printf("--%s ---\n", filename)
|
_, _ = lib.Warn(err, "couldn't read certificate")
|
||||||
if strings.HasPrefix(filename, "https://") {
|
continue
|
||||||
displayAllCertsWeb(filename, leafOnly)
|
|
||||||
} else {
|
|
||||||
in, err := os.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
lib.Warn(err, "couldn't read certificate")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
displayAllCerts(in, leafOnly)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
displayAllCerts(in, leafOnly)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,11 @@ import (
|
|||||||
// following two lifted from CFSSL, (replace-regexp "\(.+\): \(.+\),"
|
// following two lifted from CFSSL, (replace-regexp "\(.+\): \(.+\),"
|
||||||
// "\2: \1,")
|
// "\2: \1,")
|
||||||
|
|
||||||
|
const (
|
||||||
|
sSHA256 = "SHA256"
|
||||||
|
sSHA512 = "SHA512"
|
||||||
|
)
|
||||||
|
|
||||||
var keyUsage = map[x509.KeyUsage]string{
|
var keyUsage = map[x509.KeyUsage]string{
|
||||||
x509.KeyUsageDigitalSignature: "digital signature",
|
x509.KeyUsageDigitalSignature: "digital signature",
|
||||||
x509.KeyUsageContentCommitment: "content committment",
|
x509.KeyUsageContentCommitment: "content committment",
|
||||||
@@ -26,42 +31,36 @@ var keyUsage = map[x509.KeyUsage]string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var extKeyUsages = map[x509.ExtKeyUsage]string{
|
var extKeyUsages = map[x509.ExtKeyUsage]string{
|
||||||
x509.ExtKeyUsageAny: "any",
|
x509.ExtKeyUsageAny: "any",
|
||||||
x509.ExtKeyUsageServerAuth: "server auth",
|
x509.ExtKeyUsageServerAuth: "server auth",
|
||||||
x509.ExtKeyUsageClientAuth: "client auth",
|
x509.ExtKeyUsageClientAuth: "client auth",
|
||||||
x509.ExtKeyUsageCodeSigning: "code signing",
|
x509.ExtKeyUsageCodeSigning: "code signing",
|
||||||
x509.ExtKeyUsageEmailProtection: "s/mime",
|
x509.ExtKeyUsageEmailProtection: "s/mime",
|
||||||
x509.ExtKeyUsageIPSECEndSystem: "ipsec end system",
|
x509.ExtKeyUsageIPSECEndSystem: "ipsec end system",
|
||||||
x509.ExtKeyUsageIPSECTunnel: "ipsec tunnel",
|
x509.ExtKeyUsageIPSECTunnel: "ipsec tunnel",
|
||||||
x509.ExtKeyUsageIPSECUser: "ipsec user",
|
x509.ExtKeyUsageIPSECUser: "ipsec user",
|
||||||
x509.ExtKeyUsageTimeStamping: "timestamping",
|
x509.ExtKeyUsageTimeStamping: "timestamping",
|
||||||
x509.ExtKeyUsageOCSPSigning: "ocsp signing",
|
x509.ExtKeyUsageOCSPSigning: "ocsp signing",
|
||||||
x509.ExtKeyUsageMicrosoftServerGatedCrypto: "microsoft sgc",
|
x509.ExtKeyUsageMicrosoftServerGatedCrypto: "microsoft sgc",
|
||||||
x509.ExtKeyUsageNetscapeServerGatedCrypto: "netscape sgc",
|
x509.ExtKeyUsageNetscapeServerGatedCrypto: "netscape sgc",
|
||||||
}
|
x509.ExtKeyUsageMicrosoftCommercialCodeSigning: "microsoft commercial code signing",
|
||||||
|
x509.ExtKeyUsageMicrosoftKernelCodeSigning: "microsoft kernel code signing",
|
||||||
func pubKeyAlgo(a x509.PublicKeyAlgorithm) string {
|
|
||||||
switch a {
|
|
||||||
case x509.RSA:
|
|
||||||
return "RSA"
|
|
||||||
case x509.ECDSA:
|
|
||||||
return "ECDSA"
|
|
||||||
case x509.DSA:
|
|
||||||
return "DSA"
|
|
||||||
default:
|
|
||||||
return "unknown public key algorithm"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func sigAlgoPK(a x509.SignatureAlgorithm) string {
|
func sigAlgoPK(a x509.SignatureAlgorithm) string {
|
||||||
switch a {
|
switch a {
|
||||||
|
|
||||||
case x509.MD2WithRSA, x509.MD5WithRSA, x509.SHA1WithRSA, x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA:
|
case x509.MD2WithRSA, x509.MD5WithRSA, x509.SHA1WithRSA, x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA:
|
||||||
return "RSA"
|
return "RSA"
|
||||||
|
case x509.SHA256WithRSAPSS, x509.SHA384WithRSAPSS, x509.SHA512WithRSAPSS:
|
||||||
|
return "RSA-PSS"
|
||||||
case x509.ECDSAWithSHA1, x509.ECDSAWithSHA256, x509.ECDSAWithSHA384, x509.ECDSAWithSHA512:
|
case x509.ECDSAWithSHA1, x509.ECDSAWithSHA256, x509.ECDSAWithSHA384, x509.ECDSAWithSHA512:
|
||||||
return "ECDSA"
|
return "ECDSA"
|
||||||
case x509.DSAWithSHA1, x509.DSAWithSHA256:
|
case x509.DSAWithSHA1, x509.DSAWithSHA256:
|
||||||
return "DSA"
|
return "DSA"
|
||||||
|
case x509.PureEd25519:
|
||||||
|
return "Ed25519"
|
||||||
|
case x509.UnknownSignatureAlgorithm:
|
||||||
|
return "unknown public key algorithm"
|
||||||
default:
|
default:
|
||||||
return "unknown public key algorithm"
|
return "unknown public key algorithm"
|
||||||
}
|
}
|
||||||
@@ -76,11 +75,21 @@ func sigAlgoHash(a x509.SignatureAlgorithm) string {
|
|||||||
case x509.SHA1WithRSA, x509.ECDSAWithSHA1, x509.DSAWithSHA1:
|
case x509.SHA1WithRSA, x509.ECDSAWithSHA1, x509.DSAWithSHA1:
|
||||||
return "SHA1"
|
return "SHA1"
|
||||||
case x509.SHA256WithRSA, x509.ECDSAWithSHA256, x509.DSAWithSHA256:
|
case x509.SHA256WithRSA, x509.ECDSAWithSHA256, x509.DSAWithSHA256:
|
||||||
return "SHA256"
|
return sSHA256
|
||||||
|
case x509.SHA256WithRSAPSS:
|
||||||
|
return sSHA256
|
||||||
case x509.SHA384WithRSA, x509.ECDSAWithSHA384:
|
case x509.SHA384WithRSA, x509.ECDSAWithSHA384:
|
||||||
return "SHA384"
|
return "SHA384"
|
||||||
|
case x509.SHA384WithRSAPSS:
|
||||||
|
return "SHA384"
|
||||||
case x509.SHA512WithRSA, x509.ECDSAWithSHA512:
|
case x509.SHA512WithRSA, x509.ECDSAWithSHA512:
|
||||||
return "SHA512"
|
return sSHA512
|
||||||
|
case x509.SHA512WithRSAPSS:
|
||||||
|
return sSHA512
|
||||||
|
case x509.PureEd25519:
|
||||||
|
return sSHA512
|
||||||
|
case x509.UnknownSignatureAlgorithm:
|
||||||
|
return "unknown hash algorithm"
|
||||||
default:
|
default:
|
||||||
return "unknown hash algorithm"
|
return "unknown hash algorithm"
|
||||||
}
|
}
|
||||||
@@ -90,9 +99,11 @@ const maxLine = 78
|
|||||||
|
|
||||||
func makeIndent(n int) string {
|
func makeIndent(n int) string {
|
||||||
s := " "
|
s := " "
|
||||||
for i := 0; i < n; i++ {
|
var sSb97 strings.Builder
|
||||||
s += " "
|
for range n {
|
||||||
|
sSb97.WriteString(" ")
|
||||||
}
|
}
|
||||||
|
s += sSb97.String()
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +111,7 @@ func indentLen(n int) int {
|
|||||||
return 4 + (8 * n)
|
return 4 + (8 * n)
|
||||||
}
|
}
|
||||||
|
|
||||||
// this isn't real efficient, but that's not a problem here
|
// this isn't real efficient, but that's not a problem here.
|
||||||
func wrap(s string, indent int) string {
|
func wrap(s string, indent int) string {
|
||||||
if indent > 3 {
|
if indent > 3 {
|
||||||
indent = 3
|
indent = 3
|
||||||
@@ -123,9 +134,11 @@ func wrap(s string, indent int) string {
|
|||||||
|
|
||||||
func dumpHex(in []byte) string {
|
func dumpHex(in []byte) string {
|
||||||
var s string
|
var s string
|
||||||
|
var sSb130 strings.Builder
|
||||||
for i := range in {
|
for i := range in {
|
||||||
s += fmt.Sprintf("%02X:", in[i])
|
sSb130.WriteString(fmt.Sprintf("%02X:", in[i]))
|
||||||
}
|
}
|
||||||
|
s += sSb130.String()
|
||||||
|
|
||||||
return strings.Trim(s, ":")
|
return strings.Trim(s, ":")
|
||||||
}
|
}
|
||||||
@@ -136,14 +149,14 @@ func dumpHex(in []byte) string {
|
|||||||
func permissiveConfig() *tls.Config {
|
func permissiveConfig() *tls.Config {
|
||||||
return &tls.Config{
|
return &tls.Config{
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
}
|
} // #nosec G402
|
||||||
}
|
}
|
||||||
|
|
||||||
// verifyConfig returns a config that will verify the connection.
|
// verifyConfig returns a config that will verify the connection.
|
||||||
func verifyConfig(hostname string) *tls.Config {
|
func verifyConfig(hostname string) *tls.Config {
|
||||||
return &tls.Config{
|
return &tls.Config{
|
||||||
ServerName: hostname,
|
ServerName: hostname,
|
||||||
}
|
} // #nosec G402
|
||||||
}
|
}
|
||||||
|
|
||||||
type connInfo struct {
|
type connInfo struct {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -54,7 +53,7 @@ func displayName(name pkix.Name) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func expires(cert *x509.Certificate) time.Duration {
|
func expires(cert *x509.Certificate) time.Duration {
|
||||||
return cert.NotAfter.Sub(time.Now())
|
return time.Until(cert.NotAfter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func inDanger(cert *x509.Certificate) bool {
|
func inDanger(cert *x509.Certificate) bool {
|
||||||
@@ -81,15 +80,15 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
for _, file := range flag.Args() {
|
for _, file := range flag.Args() {
|
||||||
in, err := ioutil.ReadFile(file)
|
in, err := os.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lib.Warn(err, "failed to read file")
|
_, _ = lib.Warn(err, "failed to read file")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
certs, err := certlib.ParseCertificatesPEM(in)
|
certs, err := certlib.ParseCertificatesPEM(in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lib.Warn(err, "while parsing certificates")
|
_, _ = lib.Warn(err, "while parsing certificates")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,11 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.wntrmute.dev/kyle/goutils/certlib"
|
"git.wntrmute.dev/kyle/goutils/certlib"
|
||||||
"git.wntrmute.dev/kyle/goutils/certlib/revoke"
|
"git.wntrmute.dev/kyle/goutils/certlib/revoke"
|
||||||
"git.wntrmute.dev/kyle/goutils/die"
|
|
||||||
"git.wntrmute.dev/kyle/goutils/lib"
|
"git.wntrmute.dev/kyle/goutils/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,83 +28,116 @@ func printRevocation(cert *x509.Certificate) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
type appConfig struct {
|
||||||
var caFile, intFile string
|
caFile, intFile string
|
||||||
var forceIntermediateBundle, revexp, verbose bool
|
forceIntermediateBundle bool
|
||||||
flag.StringVar(&caFile, "ca", "", "CA certificate `bundle`")
|
revexp, verbose bool
|
||||||
flag.StringVar(&intFile, "i", "", "intermediate `bundle`")
|
}
|
||||||
flag.BoolVar(&forceIntermediateBundle, "f", false,
|
|
||||||
|
func parseFlags() appConfig {
|
||||||
|
var cfg appConfig
|
||||||
|
flag.StringVar(&cfg.caFile, "ca", "", "CA certificate `bundle`")
|
||||||
|
flag.StringVar(&cfg.intFile, "i", "", "intermediate `bundle`")
|
||||||
|
flag.BoolVar(&cfg.forceIntermediateBundle, "f", false,
|
||||||
"force the use of the intermediate bundle, ignoring any intermediates bundled with certificate")
|
"force the use of the intermediate bundle, ignoring any intermediates bundled with certificate")
|
||||||
flag.BoolVar(&revexp, "r", false, "print revocation and expiry information")
|
flag.BoolVar(&cfg.revexp, "r", false, "print revocation and expiry information")
|
||||||
flag.BoolVar(&verbose, "v", false, "verbose")
|
flag.BoolVar(&cfg.verbose, "v", false, "verbose")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
var roots *x509.CertPool
|
func loadRoots(caFile string, verbose bool) (*x509.CertPool, error) {
|
||||||
if caFile != "" {
|
if caFile == "" {
|
||||||
var err error
|
return x509.SystemCertPool()
|
||||||
if verbose {
|
|
||||||
fmt.Println("[+] loading root certificates from", caFile)
|
|
||||||
}
|
|
||||||
roots, err = certlib.LoadPEMCertPool(caFile)
|
|
||||||
die.If(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var ints *x509.CertPool
|
|
||||||
if intFile != "" {
|
|
||||||
var err error
|
|
||||||
if verbose {
|
|
||||||
fmt.Println("[+] loading intermediate certificates from", intFile)
|
|
||||||
}
|
|
||||||
ints, err = certlib.LoadPEMCertPool(caFile)
|
|
||||||
die.If(err)
|
|
||||||
} else {
|
|
||||||
ints = x509.NewCertPool()
|
|
||||||
}
|
|
||||||
|
|
||||||
if flag.NArg() != 1 {
|
|
||||||
fmt.Fprintf(os.Stderr, "Usage: %s [-ca bundle] [-i bundle] cert",
|
|
||||||
lib.ProgName())
|
|
||||||
}
|
|
||||||
|
|
||||||
fileData, err := ioutil.ReadFile(flag.Arg(0))
|
|
||||||
die.If(err)
|
|
||||||
|
|
||||||
chain, err := certlib.ParseCertificatesPEM(fileData)
|
|
||||||
die.If(err)
|
|
||||||
if verbose {
|
if verbose {
|
||||||
fmt.Printf("[+] %s has %d certificates\n", flag.Arg(0), len(chain))
|
fmt.Println("[+] loading root certificates from", caFile)
|
||||||
}
|
}
|
||||||
|
return certlib.LoadPEMCertPool(caFile)
|
||||||
|
}
|
||||||
|
|
||||||
cert := chain[0]
|
func loadIntermediates(intFile string, verbose bool) (*x509.CertPool, error) {
|
||||||
if len(chain) > 1 {
|
if intFile == "" {
|
||||||
if !forceIntermediateBundle {
|
return x509.NewCertPool(), nil
|
||||||
for _, intermediate := range chain[1:] {
|
}
|
||||||
if verbose {
|
if verbose {
|
||||||
fmt.Printf("[+] adding intermediate with SKI %x\n", intermediate.SubjectKeyId)
|
fmt.Println("[+] loading intermediate certificates from", intFile)
|
||||||
}
|
}
|
||||||
|
// Note: use intFile here (previously used caFile mistakenly)
|
||||||
|
return certlib.LoadPEMCertPool(intFile)
|
||||||
|
}
|
||||||
|
|
||||||
ints.AddCert(intermediate)
|
func addBundledIntermediates(chain []*x509.Certificate, pool *x509.CertPool, verbose bool) {
|
||||||
}
|
for _, intermediate := range chain[1:] {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf("[+] adding intermediate with SKI %x\n", intermediate.SubjectKeyId)
|
||||||
}
|
}
|
||||||
|
pool.AddCert(intermediate)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyCert(cert *x509.Certificate, roots, ints *x509.CertPool) error {
|
||||||
opts := x509.VerifyOptions{
|
opts := x509.VerifyOptions{
|
||||||
Intermediates: ints,
|
Intermediates: ints,
|
||||||
Roots: roots,
|
Roots: roots,
|
||||||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
||||||
}
|
}
|
||||||
|
_, err := cert.Verify(opts)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
_, err = cert.Verify(opts)
|
func run(cfg appConfig) error {
|
||||||
|
roots, err := loadRoots(cfg.caFile, cfg.verbose)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Verification failed: %v\n", err)
|
return err
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if verbose {
|
ints, err := loadIntermediates(cfg.intFile, cfg.verbose)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if flag.NArg() != 1 {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage: %s [-ca bundle] [-i bundle] cert", lib.ProgName())
|
||||||
|
}
|
||||||
|
|
||||||
|
fileData, err := os.ReadFile(flag.Arg(0))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
chain, err := certlib.ParseCertificatesPEM(fileData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if cfg.verbose {
|
||||||
|
fmt.Printf("[+] %s has %d certificates\n", flag.Arg(0), len(chain))
|
||||||
|
}
|
||||||
|
|
||||||
|
cert := chain[0]
|
||||||
|
if len(chain) > 1 && !cfg.forceIntermediateBundle {
|
||||||
|
addBundledIntermediates(chain, ints, cfg.verbose)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = verifyCert(cert, roots, ints); err != nil {
|
||||||
|
return fmt.Errorf("certificate verification failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.verbose {
|
||||||
fmt.Println("OK")
|
fmt.Println("OK")
|
||||||
}
|
}
|
||||||
|
|
||||||
if revexp {
|
if cfg.revexp {
|
||||||
printRevocation(cert)
|
printRevocation(cert)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cfg := parseFlags()
|
||||||
|
if err := run(cfg); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -56,7 +58,7 @@ var modes = ssh.TerminalModes{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sshAgent() ssh.AuthMethod {
|
func sshAgent() ssh.AuthMethod {
|
||||||
a, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
|
a, err := (&net.Dialer{}).DialContext(context.Background(), "unix", os.Getenv("SSH_AUTH_SOCK"))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return ssh.PublicKeysCallback(agent.NewClient(a).Signers)
|
return ssh.PublicKeysCallback(agent.NewClient(a).Signers)
|
||||||
}
|
}
|
||||||
@@ -82,7 +84,7 @@ func scanner(host string, in io.Reader, out io.Writer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func logError(host string, err error, format string, args ...interface{}) {
|
func logError(host string, err error, format string, args ...any) {
|
||||||
msg := fmt.Sprintf(format, args...)
|
msg := fmt.Sprintf(format, args...)
|
||||||
log.Printf("[%s] FAILED: %s: %v\n", host, msg, err)
|
log.Printf("[%s] FAILED: %s: %v\n", host, msg, err)
|
||||||
}
|
}
|
||||||
@@ -93,7 +95,7 @@ func exec(wg *sync.WaitGroup, user, host string, commands []string) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
for i := len(shutdown) - 1; i >= 0; i-- {
|
for i := len(shutdown) - 1; i >= 0; i-- {
|
||||||
err := shutdown[i]()
|
err := shutdown[i]()
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
logError(host, err, "shutting down")
|
logError(host, err, "shutting down")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,7 +117,7 @@ func exec(wg *sync.WaitGroup, user, host string, commands []string) {
|
|||||||
}
|
}
|
||||||
shutdown = append(shutdown, session.Close)
|
shutdown = append(shutdown, session.Close)
|
||||||
|
|
||||||
if err := session.RequestPty("xterm", 80, 40, modes); err != nil {
|
if err = session.RequestPty("xterm", 80, 40, modes); err != nil {
|
||||||
session.Close()
|
session.Close()
|
||||||
logError(host, err, "request for pty failed")
|
logError(host, err, "request for pty failed")
|
||||||
return
|
return
|
||||||
@@ -150,7 +152,7 @@ func upload(wg *sync.WaitGroup, user, host, local, remote string) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
for i := len(shutdown) - 1; i >= 0; i-- {
|
for i := len(shutdown) - 1; i >= 0; i-- {
|
||||||
err := shutdown[i]()
|
err := shutdown[i]()
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
logError(host, err, "shutting down")
|
logError(host, err, "shutting down")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,7 +201,7 @@ func upload(wg *sync.WaitGroup, user, host, local, remote string) {
|
|||||||
fmt.Printf("[%s] wrote %d-byte chunk\n", host, n)
|
fmt.Printf("[%s] wrote %d-byte chunk\n", host, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == io.EOF {
|
if errors.Is(err, io.EOF) {
|
||||||
break
|
break
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
logError(host, err, "reading chunk")
|
logError(host, err, "reading chunk")
|
||||||
@@ -215,7 +217,7 @@ func download(wg *sync.WaitGroup, user, host, local, remote string) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
for i := len(shutdown) - 1; i >= 0; i-- {
|
for i := len(shutdown) - 1; i >= 0; i-- {
|
||||||
err := shutdown[i]()
|
err := shutdown[i]()
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
logError(host, err, "shutting down")
|
logError(host, err, "shutting down")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -265,7 +267,7 @@ func download(wg *sync.WaitGroup, user, host, local, remote string) {
|
|||||||
fmt.Printf("[%s] wrote %d-byte chunk\n", host, n)
|
fmt.Printf("[%s] wrote %d-byte chunk\n", host, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == io.EOF {
|
if errors.Is(err, io.EOF) {
|
||||||
break
|
break
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
logError(host, err, "reading chunk")
|
logError(host, err, "reading chunk")
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.wntrmute.dev/kyle/goutils/die"
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
"git.wntrmute.dev/kyle/goutils/fileutil"
|
"git.wntrmute.dev/kyle/goutils/fileutil"
|
||||||
@@ -26,7 +27,7 @@ func setupFile(hdr *tar.Header, file *os.File) error {
|
|||||||
if verbose {
|
if verbose {
|
||||||
fmt.Printf("\tchmod %0#o\n", hdr.Mode)
|
fmt.Printf("\tchmod %0#o\n", hdr.Mode)
|
||||||
}
|
}
|
||||||
err := file.Chmod(os.FileMode(hdr.Mode))
|
err := file.Chmod(os.FileMode(hdr.Mode & 0xFFFFFFFF)) // #nosec G115
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -48,73 +49,105 @@ func linkTarget(target, top string) string {
|
|||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
|
|
||||||
return filepath.Clean(filepath.Join(target, top))
|
return filepath.Clean(filepath.Join(top, target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// safeJoin joins base and elem and ensures the resulting path does not escape base.
|
||||||
|
func safeJoin(base, elem string) (string, error) {
|
||||||
|
cleanBase := filepath.Clean(base)
|
||||||
|
joined := filepath.Clean(filepath.Join(cleanBase, elem))
|
||||||
|
|
||||||
|
absBase, err := filepath.Abs(cleanBase)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
absJoined, err := filepath.Abs(joined)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
rel, err := filepath.Rel(absBase, absJoined)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
|
||||||
|
return "", fmt.Errorf("path traversal detected: %s escapes %s", elem, base)
|
||||||
|
}
|
||||||
|
return joined, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleTypeReg(tfr *tar.Reader, hdr *tar.Header, filePath string) error {
|
||||||
|
file, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
if _, err = io.Copy(file, tfr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return setupFile(hdr, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleTypeLink(hdr *tar.Header, top, filePath string) error {
|
||||||
|
file, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
srcPath, err := safeJoin(top, hdr.Linkname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
source, err := os.Open(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer source.Close()
|
||||||
|
|
||||||
|
if _, err = io.Copy(file, source); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return setupFile(hdr, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleTypeSymlink(hdr *tar.Header, top, filePath string) error {
|
||||||
|
if !fileutil.ValidateSymlink(hdr.Linkname, top) {
|
||||||
|
return fmt.Errorf("symlink %s is outside the top-level %s", hdr.Linkname, top)
|
||||||
|
}
|
||||||
|
path := linkTarget(hdr.Linkname, top)
|
||||||
|
if ok, err := filepath.Match(top+"/*", filepath.Clean(path)); !ok {
|
||||||
|
return fmt.Errorf("symlink %s isn't in %s", hdr.Linkname, top)
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.Symlink(linkTarget(hdr.Linkname, top), filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleTypeDir(hdr *tar.Header, filePath string) error {
|
||||||
|
return os.MkdirAll(filePath, os.FileMode(hdr.Mode&0xFFFFFFFF)) // #nosec G115
|
||||||
}
|
}
|
||||||
|
|
||||||
func processFile(tfr *tar.Reader, hdr *tar.Header, top string) error {
|
func processFile(tfr *tar.Reader, hdr *tar.Header, top string) error {
|
||||||
if verbose {
|
if verbose {
|
||||||
fmt.Println(hdr.Name)
|
fmt.Println(hdr.Name)
|
||||||
}
|
}
|
||||||
filePath := filepath.Clean(filepath.Join(top, hdr.Name))
|
|
||||||
switch hdr.Typeflag {
|
|
||||||
case tar.TypeReg:
|
|
||||||
file, err := os.Create(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(file, tfr)
|
filePath, err := safeJoin(top, hdr.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
err = setupFile(hdr, file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case tar.TypeLink:
|
|
||||||
file, err := os.Create(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
source, err := os.Open(hdr.Linkname)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(file, source)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = setupFile(hdr, file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case tar.TypeSymlink:
|
|
||||||
if !fileutil.ValidateSymlink(hdr.Linkname, top) {
|
|
||||||
return fmt.Errorf("symlink %s is outside the top-level %s",
|
|
||||||
hdr.Linkname, top)
|
|
||||||
}
|
|
||||||
path := linkTarget(hdr.Linkname, top)
|
|
||||||
if ok, err := filepath.Match(top+"/*", filepath.Clean(path)); !ok {
|
|
||||||
return fmt.Errorf("symlink %s isn't in %s", hdr.Linkname, top)
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err := os.Symlink(linkTarget(hdr.Linkname, top), filePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case tar.TypeDir:
|
|
||||||
err := os.MkdirAll(filePath, os.FileMode(hdr.Mode))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch hdr.Typeflag {
|
||||||
|
case tar.TypeReg:
|
||||||
|
return handleTypeReg(tfr, hdr, filePath)
|
||||||
|
case tar.TypeLink:
|
||||||
|
return handleTypeLink(hdr, top, filePath)
|
||||||
|
case tar.TypeSymlink:
|
||||||
|
return handleTypeSymlink(hdr, top, filePath)
|
||||||
|
case tar.TypeDir:
|
||||||
|
return handleTypeDir(hdr, filePath)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,16 +294,16 @@ func main() {
|
|||||||
die.If(err)
|
die.If(err)
|
||||||
|
|
||||||
tfr := tar.NewReader(r)
|
tfr := tar.NewReader(r)
|
||||||
|
var hdr *tar.Header
|
||||||
for {
|
for {
|
||||||
hdr, err := tfr.Next()
|
hdr, err = tfr.Next()
|
||||||
if err == io.EOF {
|
if errors.Is(err, io.EOF) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
die.If(err)
|
die.If(err)
|
||||||
|
|
||||||
err = processFile(tfr, hdr, top)
|
err = processFile(tfr, hdr, top)
|
||||||
die.If(err)
|
die.If(err)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Close()
|
r.Close()
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import (
|
|||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"log"
|
|
||||||
|
|
||||||
"git.wntrmute.dev/kyle/goutils/die"
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
)
|
)
|
||||||
@@ -17,12 +16,12 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
for _, fileName := range flag.Args() {
|
for _, fileName := range flag.Args() {
|
||||||
in, err := ioutil.ReadFile(fileName)
|
in, err := os.ReadFile(fileName)
|
||||||
die.If(err)
|
die.If(err)
|
||||||
|
|
||||||
if p, _ := pem.Decode(in); p != nil {
|
if p, _ := pem.Decode(in); p != nil {
|
||||||
if p.Type != "CERTIFICATE REQUEST" {
|
if p.Type != "CERTIFICATE REQUEST" {
|
||||||
log.Fatal("INVALID FILE TYPE")
|
die.With("INVALID FILE TYPE")
|
||||||
}
|
}
|
||||||
in = p.Bytes
|
in = p.Bytes
|
||||||
}
|
}
|
||||||
@@ -48,8 +47,8 @@ func main() {
|
|||||||
Bytes: out,
|
Bytes: out,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ioutil.WriteFile(fileName+".pub", pem.EncodeToMemory(p), 0644)
|
err = os.WriteFile(fileName+".pub", pem.EncodeToMemory(p), 0o644) // #nosec G306
|
||||||
die.If(err)
|
die.If(err)
|
||||||
fmt.Printf("[+] wrote %s.\n", fileName+".pub")
|
fmt.Fprintf(os.Stdout, "[+] wrote %s.\n", fileName+".pub")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -152,7 +153,7 @@ func rsync(syncDir, target, excludeFile string, verboseRsync bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(path, args...)
|
cmd := exec.CommandContext(context.Background(), path, args...)
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
@@ -163,7 +164,6 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
var logLevel, mountDir, syncDir, target string
|
var logLevel, mountDir, syncDir, target string
|
||||||
var dryRun, quietMode, noSyslog, verboseRsync bool
|
var dryRun, quietMode, noSyslog, verboseRsync bool
|
||||||
|
|
||||||
@@ -219,7 +219,7 @@ func main() {
|
|||||||
if excludeFile != "" {
|
if excludeFile != "" {
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Infof("removing exclude file %s", excludeFile)
|
log.Infof("removing exclude file %s", excludeFile)
|
||||||
if err := os.Remove(excludeFile); err != nil {
|
if rmErr := os.Remove(excludeFile); rmErr != nil {
|
||||||
log.Warningf("failed to remove temp file %s", excludeFile)
|
log.Warningf("failed to remove temp file %s", excludeFile)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -15,43 +15,41 @@ import (
|
|||||||
const defaultHashAlgorithm = "sha256"
|
const defaultHashAlgorithm = "sha256"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
hAlgo string
|
hAlgo string
|
||||||
debug = dbg.New()
|
debug = dbg.New()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func openImage(imageFile string) (*os.File, []byte, error) {
|
||||||
func openImage(imageFile string) (image *os.File, hash []byte, err error) {
|
f, err := os.Open(imageFile)
|
||||||
image, err = os.Open(imageFile)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
hash, err = ahash.SumReader(hAlgo, image)
|
h, err := ahash.SumReader(hAlgo, f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = image.Seek(0, 0)
|
if _, err = f.Seek(0, 0); err != nil {
|
||||||
if err != nil {
|
return nil, nil, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debug.Printf("%s %x\n", imageFile, hash)
|
debug.Printf("%s %x\n", imageFile, h)
|
||||||
return
|
return f, h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func openDevice(devicePath string) (device *os.File, err error) {
|
func openDevice(devicePath string) (*os.File, error) {
|
||||||
fi, err := os.Stat(devicePath)
|
fi, err := os.Stat(devicePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
device, err = os.OpenFile(devicePath, os.O_RDWR|os.O_SYNC, fi.Mode())
|
device, err := os.OpenFile(devicePath, os.O_RDWR|os.O_SYNC, fi.Mode())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return device, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -105,12 +103,12 @@ func main() {
|
|||||||
die.If(err)
|
die.If(err)
|
||||||
|
|
||||||
if !bytes.Equal(deviceHash, hash) {
|
if !bytes.Equal(deviceHash, hash) {
|
||||||
fmt.Fprintln(os.Stderr, "Hash mismatch:")
|
buf := &bytes.Buffer{}
|
||||||
fmt.Fprintf(os.Stderr, "\t%s: %s\n", imageFile, hash)
|
fmt.Fprintln(buf, "Hash mismatch:")
|
||||||
fmt.Fprintf(os.Stderr, "\t%s: %s\n", devicePath, deviceHash)
|
fmt.Fprintf(buf, "\t%s: %s\n", imageFile, hash)
|
||||||
os.Exit(1)
|
fmt.Fprintf(buf, "\t%s: %s\n", devicePath, deviceHash)
|
||||||
|
die.With(buf.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
debug.Println("OK")
|
debug.Println("OK")
|
||||||
os.Exit(0)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,33 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.wntrmute.dev/kyle/goutils/die"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
)
|
)
|
||||||
|
|
||||||
func usage(w io.Writer, exc int) {
|
func usage(w io.Writer, exc int) {
|
||||||
fmt.Fprintln(w, `usage: dumpbytes <file>`)
|
fmt.Fprintln(w, `usage: dumpbytes -n tabs <file>`)
|
||||||
os.Exit(exc)
|
os.Exit(exc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func printBytes(buf []byte) {
|
func printBytes(buf []byte) {
|
||||||
fmt.Printf("\t")
|
fmt.Printf("\t")
|
||||||
for i := 0; i < len(buf); i++ {
|
for i := range buf {
|
||||||
fmt.Printf("0x%02x, ", buf[i])
|
fmt.Printf("0x%02x, ", buf[i])
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpFile(path string, indentLevel int) error {
|
func dumpFile(path string, indentLevel int) error {
|
||||||
indent := ""
|
var indent strings.Builder
|
||||||
for i := 0; i < indentLevel; i++ {
|
for range indentLevel {
|
||||||
indent += "\t"
|
indent.WriteByte('\t')
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
@@ -34,13 +37,14 @@ func dumpFile(path string, indentLevel int) error {
|
|||||||
|
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
fmt.Printf("%svar buffer = []byte{\n", indent)
|
fmt.Printf("%svar buffer = []byte{\n", indent.String())
|
||||||
|
var n int
|
||||||
for {
|
for {
|
||||||
buf := make([]byte, 8)
|
buf := make([]byte, 8)
|
||||||
n, err := file.Read(buf)
|
n, err = file.Read(buf)
|
||||||
if err == io.EOF {
|
if errors.Is(err, io.EOF) {
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
fmt.Printf("%s", indent)
|
fmt.Printf("%s", indent.String())
|
||||||
printBytes(buf[:n])
|
printBytes(buf[:n])
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@@ -50,11 +54,11 @@ func dumpFile(path string, indentLevel int) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s", indent)
|
fmt.Printf("%s", indent.String())
|
||||||
printBytes(buf[:n])
|
printBytes(buf[:n])
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s}\n", indent)
|
fmt.Printf("%s}\n", indent.String())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"git.wntrmute.dev/kyle/goutils/die"
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
)
|
)
|
||||||
|
|
||||||
// size of a kilobit in bytes
|
// size of a kilobit in bytes.
|
||||||
const kilobit = 128
|
const kilobit = 128
|
||||||
const pageSize = 4096
|
const pageSize = 4096
|
||||||
|
|
||||||
@@ -26,10 +26,10 @@ func main() {
|
|||||||
path = flag.Arg(0)
|
path = flag.Arg(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fillByte := uint8(*fill)
|
fillByte := uint8(*fill & 0xff) // #nosec G115 clearing out of bounds bits
|
||||||
|
|
||||||
buf := make([]byte, pageSize)
|
buf := make([]byte, pageSize)
|
||||||
for i := 0; i < pageSize; i++ {
|
for i := range pageSize {
|
||||||
buf[i] = fillByte
|
buf[i] = fillByte
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ func main() {
|
|||||||
die.If(err)
|
die.If(err)
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
for i := 0; i < pages; i++ {
|
for range pages {
|
||||||
_, err = file.Write(buf)
|
_, err = file.Write(buf)
|
||||||
die.If(err)
|
die.If(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,15 +72,13 @@ func main() {
|
|||||||
|
|
||||||
if end < start {
|
if end < start {
|
||||||
fmt.Fprintln(os.Stderr, "[!] end < start, swapping values")
|
fmt.Fprintln(os.Stderr, "[!] end < start, swapping values")
|
||||||
tmp := end
|
start, end = end, start
|
||||||
end = start
|
|
||||||
start = tmp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var fmtStr string
|
var fmtStr string
|
||||||
|
|
||||||
if !*quiet {
|
if !*quiet {
|
||||||
maxLine := fmt.Sprintf("%d", len(lines))
|
maxLine := strconv.Itoa(len(lines))
|
||||||
fmtStr = fmt.Sprintf("%%0%dd: %%s", len(maxLine))
|
fmtStr = fmt.Sprintf("%%0%dd: %%s", len(maxLine))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,9 +96,9 @@ func main() {
|
|||||||
fmtStr += "\n"
|
fmtStr += "\n"
|
||||||
for i := start; !endFunc(i); i++ {
|
for i := start; !endFunc(i); i++ {
|
||||||
if *quiet {
|
if *quiet {
|
||||||
fmt.Println(lines[i])
|
fmt.Fprintln(os.Stdout, lines[i])
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(fmtStr, i, lines[i])
|
fmt.Fprintf(os.Stdout, fmtStr, i, lines[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@@ -8,7 +9,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func lookupHost(host string) error {
|
func lookupHost(host string) error {
|
||||||
cname, err := net.LookupCNAME(host)
|
r := &net.Resolver{}
|
||||||
|
cname, err := r.LookupCNAME(context.Background(), host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -18,7 +20,7 @@ func lookupHost(host string) error {
|
|||||||
host = cname
|
host = cname
|
||||||
}
|
}
|
||||||
|
|
||||||
addrs, err := net.LookupHost(host)
|
addrs, err := r.LookupHost(context.Background(), host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.wntrmute.dev/kyle/goutils/lib"
|
"git.wntrmute.dev/kyle/goutils/lib"
|
||||||
@@ -16,20 +16,20 @@ func prettify(file string, validateOnly bool) error {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
if file == "-" {
|
if file == "-" {
|
||||||
in, err = ioutil.ReadAll(os.Stdin)
|
in, err = io.ReadAll(os.Stdin)
|
||||||
} else {
|
} else {
|
||||||
in, err = ioutil.ReadFile(file)
|
in, err = os.ReadFile(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lib.Warn(err, "ReadFile")
|
_, _ = lib.Warn(err, "ReadFile")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf = &bytes.Buffer{}
|
var buf = &bytes.Buffer{}
|
||||||
err = json.Indent(buf, in, "", " ")
|
err = json.Indent(buf, in, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lib.Warn(err, "%s", file)
|
_, _ = lib.Warn(err, "%s", file)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,11 +40,11 @@ func prettify(file string, validateOnly bool) error {
|
|||||||
if file == "-" {
|
if file == "-" {
|
||||||
_, err = os.Stdout.Write(buf.Bytes())
|
_, err = os.Stdout.Write(buf.Bytes())
|
||||||
} else {
|
} else {
|
||||||
err = ioutil.WriteFile(file, buf.Bytes(), 0644)
|
err = os.WriteFile(file, buf.Bytes(), 0o644)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lib.Warn(err, "WriteFile")
|
_, _ = lib.Warn(err, "WriteFile")
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
@@ -55,20 +55,20 @@ func compact(file string, validateOnly bool) error {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
if file == "-" {
|
if file == "-" {
|
||||||
in, err = ioutil.ReadAll(os.Stdin)
|
in, err = io.ReadAll(os.Stdin)
|
||||||
} else {
|
} else {
|
||||||
in, err = ioutil.ReadFile(file)
|
in, err = os.ReadFile(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lib.Warn(err, "ReadFile")
|
_, _ = lib.Warn(err, "ReadFile")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf = &bytes.Buffer{}
|
var buf = &bytes.Buffer{}
|
||||||
err = json.Compact(buf, in)
|
err = json.Compact(buf, in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lib.Warn(err, "%s", file)
|
_, _ = lib.Warn(err, "%s", file)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,11 +79,11 @@ func compact(file string, validateOnly bool) error {
|
|||||||
if file == "-" {
|
if file == "-" {
|
||||||
_, err = os.Stdout.Write(buf.Bytes())
|
_, err = os.Stdout.Write(buf.Bytes())
|
||||||
} else {
|
} else {
|
||||||
err = ioutil.WriteFile(file, buf.Bytes(), 0644)
|
err = os.WriteFile(file, buf.Bytes(), 0o644)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lib.Warn(err, "WriteFile")
|
_, _ = lib.Warn(err, "WriteFile")
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
@@ -91,7 +91,7 @@ func compact(file string, validateOnly bool) error {
|
|||||||
|
|
||||||
func usage() {
|
func usage() {
|
||||||
progname := lib.ProgName()
|
progname := lib.ProgName()
|
||||||
fmt.Printf(`Usage: %s [-h] files...
|
fmt.Fprintf(os.Stdout, `Usage: %s [-h] files...
|
||||||
%s is used to lint and prettify (or compact) JSON files. The
|
%s is used to lint and prettify (or compact) JSON files. The
|
||||||
files will be updated in-place.
|
files will be updated in-place.
|
||||||
|
|
||||||
@@ -100,7 +100,6 @@ func usage() {
|
|||||||
-h Print this help message.
|
-h Print this help message.
|
||||||
-n Don't prettify; only perform validation.
|
-n Don't prettify; only perform validation.
|
||||||
`, progname, progname)
|
`, progname, progname)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -11,7 +11,10 @@ based on whether the source filename ends in ".gz".
|
|||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
-l level Compression level (0-9). Only meaninful when
|
-l level Compression level (0-9). Only meaninful when
|
||||||
compressing a file.
|
compressing a file.
|
||||||
|
-u Do not restrict the size during decompression. As
|
||||||
|
a safeguard against gzip bombs, the maximum size
|
||||||
|
allowed is 32 * the compressed file size.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
146
cmd/kgz/main.go
146
cmd/kgz/main.go
@@ -1,68 +1,84 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"compress/flate"
|
"compress/flate"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const gzipExt = ".gz"
|
const gzipExt = ".gz"
|
||||||
|
|
||||||
func compress(path, target string, level int) error {
|
func compress(path, target string, level int) error {
|
||||||
sourceFile, err := os.Open(path)
|
sourceFile, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("opening file for read: %w", err)
|
return fmt.Errorf("opening file for read: %w", err)
|
||||||
}
|
}
|
||||||
defer sourceFile.Close()
|
defer sourceFile.Close()
|
||||||
|
|
||||||
destFile, err := os.Create(target)
|
destFile, err := os.Create(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("opening file for write: %w", err)
|
return fmt.Errorf("opening file for write: %w", err)
|
||||||
}
|
}
|
||||||
defer destFile.Close()
|
defer destFile.Close()
|
||||||
|
|
||||||
gzipCompressor, err := gzip.NewWriterLevel(destFile, level)
|
gzipCompressor, err := gzip.NewWriterLevel(destFile, level)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid compression level: %w", err)
|
return fmt.Errorf("invalid compression level: %w", err)
|
||||||
}
|
}
|
||||||
defer gzipCompressor.Close()
|
defer gzipCompressor.Close()
|
||||||
|
|
||||||
_, err = io.Copy(gzipCompressor, sourceFile)
|
_, err = io.Copy(gzipCompressor, sourceFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("compressing file: %w", err)
|
return fmt.Errorf("compressing file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func uncompress(path, target string) error {
|
func uncompress(path, target string, unrestrict bool) error {
|
||||||
sourceFile, err := os.Open(path)
|
sourceFile, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("opening file for read: %w", err)
|
return fmt.Errorf("opening file for read: %w", err)
|
||||||
}
|
}
|
||||||
defer sourceFile.Close()
|
defer sourceFile.Close()
|
||||||
|
|
||||||
gzipUncompressor, err := gzip.NewReader(sourceFile)
|
fi, err := sourceFile.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("reading gzip headers: %w", err)
|
return fmt.Errorf("reading file stats: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maxDecompressionSize := fi.Size() * 32
|
||||||
|
|
||||||
|
gzipUncompressor, err := gzip.NewReader(sourceFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("reading gzip headers: %w", err)
|
||||||
|
}
|
||||||
defer gzipUncompressor.Close()
|
defer gzipUncompressor.Close()
|
||||||
|
|
||||||
destFile, err := os.Create(target)
|
var reader io.Reader = &io.LimitedReader{
|
||||||
if err != nil {
|
R: gzipUncompressor,
|
||||||
return fmt.Errorf("opening file for write: %w", err)
|
N: maxDecompressionSize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if unrestrict {
|
||||||
|
reader = gzipUncompressor
|
||||||
|
}
|
||||||
|
|
||||||
|
destFile, err := os.Create(target)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("opening file for write: %w", err)
|
||||||
|
}
|
||||||
defer destFile.Close()
|
defer destFile.Close()
|
||||||
|
|
||||||
_, err = io.Copy(destFile, gzipUncompressor)
|
_, err = io.Copy(destFile, reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("uncompressing file: %w", err)
|
return fmt.Errorf("uncompressing file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -87,8 +103,8 @@ func isDir(path string) bool {
|
|||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
stat, err := file.Stat()
|
stat, err2 := file.Stat()
|
||||||
if err != nil {
|
if err2 != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,9 +122,9 @@ func pathForUncompressing(source, dest string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
source = filepath.Base(source)
|
source = filepath.Base(source)
|
||||||
if !strings.HasSuffix(source, gzipExt) {
|
if !strings.HasSuffix(source, gzipExt) {
|
||||||
return "", fmt.Errorf("%s is a not gzip-compressed file", source)
|
return "", fmt.Errorf("%s is a not gzip-compressed file", source)
|
||||||
}
|
}
|
||||||
outFile := source[:len(source)-len(gzipExt)]
|
outFile := source[:len(source)-len(gzipExt)]
|
||||||
outFile = filepath.Join(dest, outFile)
|
outFile = filepath.Join(dest, outFile)
|
||||||
return outFile, nil
|
return outFile, nil
|
||||||
@@ -120,9 +136,9 @@ func pathForCompressing(source, dest string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
source = filepath.Base(source)
|
source = filepath.Base(source)
|
||||||
if strings.HasSuffix(source, gzipExt) {
|
if strings.HasSuffix(source, gzipExt) {
|
||||||
return "", fmt.Errorf("%s is a gzip-compressed file", source)
|
return "", fmt.Errorf("%s is a gzip-compressed file", source)
|
||||||
}
|
}
|
||||||
|
|
||||||
dest = filepath.Join(dest, source+gzipExt)
|
dest = filepath.Join(dest, source+gzipExt)
|
||||||
return dest, nil
|
return dest, nil
|
||||||
@@ -132,8 +148,11 @@ func main() {
|
|||||||
var level int
|
var level int
|
||||||
var path string
|
var path string
|
||||||
var target = "."
|
var target = "."
|
||||||
|
var err error
|
||||||
|
var unrestrict bool
|
||||||
|
|
||||||
flag.IntVar(&level, "l", flate.DefaultCompression, "compression level")
|
flag.IntVar(&level, "l", flate.DefaultCompression, "compression level")
|
||||||
|
flag.BoolVar(&unrestrict, "u", false, "do not restrict decompression")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if flag.NArg() < 1 || flag.NArg() > 2 {
|
if flag.NArg() < 1 || flag.NArg() > 2 {
|
||||||
@@ -147,30 +166,31 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasSuffix(path, gzipExt) {
|
if strings.HasSuffix(path, gzipExt) {
|
||||||
target, err := pathForUncompressing(path, target)
|
target, err = pathForUncompressing(path, target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = uncompress(path, target)
|
err = uncompress(path, target, unrestrict)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
os.Remove(target)
|
os.Remove(target)
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
} else {
|
return
|
||||||
target, err := pathForCompressing(path, target)
|
}
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = compress(path, target, level)
|
target, err = pathForCompressing(path, target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
os.Remove(target)
|
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
os.Exit(1)
|
||||||
os.Exit(1)
|
}
|
||||||
}
|
|
||||||
|
err = compress(path, target, level)
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(target)
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,14 +40,14 @@ func main() {
|
|||||||
usage()
|
usage()
|
||||||
}
|
}
|
||||||
|
|
||||||
min, err := strconv.Atoi(flag.Arg(1))
|
minVal, err := strconv.Atoi(flag.Arg(1))
|
||||||
dieIf(err)
|
dieIf(err)
|
||||||
|
|
||||||
max, err := strconv.Atoi(flag.Arg(2))
|
maxVal, err := strconv.Atoi(flag.Arg(2))
|
||||||
dieIf(err)
|
dieIf(err)
|
||||||
|
|
||||||
code := kind << 6
|
code := kind << 6
|
||||||
code += (min << 3)
|
code += (minVal << 3)
|
||||||
code += max
|
code += maxVal
|
||||||
fmt.Printf("%0o\n", code)
|
fmt.Fprintf(os.Stdout, "%0o\n", code)
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -47,7 +46,7 @@ func help(w io.Writer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadDatabase() {
|
func loadDatabase() {
|
||||||
data, err := ioutil.ReadFile(dbFile)
|
data, err := os.ReadFile(dbFile)
|
||||||
if err != nil && os.IsNotExist(err) {
|
if err != nil && os.IsNotExist(err) {
|
||||||
partsDB = &database{
|
partsDB = &database{
|
||||||
Version: dbVersion,
|
Version: dbVersion,
|
||||||
@@ -74,7 +73,7 @@ func writeDB() {
|
|||||||
data, err := json.Marshal(partsDB)
|
data, err := json.Marshal(partsDB)
|
||||||
die.If(err)
|
die.If(err)
|
||||||
|
|
||||||
err = ioutil.WriteFile(dbFile, data, 0644)
|
err = os.WriteFile(dbFile, data, 0644)
|
||||||
die.If(err)
|
die.If(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,13 @@ import (
|
|||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ext = ".bin"
|
var ext = ".bin"
|
||||||
|
|
||||||
func stripPEM(path string) error {
|
func stripPEM(path string) error {
|
||||||
data, err := ioutil.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -22,7 +21,7 @@ func stripPEM(path string) error {
|
|||||||
fmt.Fprintf(os.Stderr, " (only the first object will be decoded)\n")
|
fmt.Fprintf(os.Stderr, " (only the first object will be decoded)\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
return ioutil.WriteFile(path+ext, p.Bytes, 0644)
|
return os.WriteFile(path+ext, p.Bytes, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.wntrmute.dev/kyle/goutils/lib"
|
"git.wntrmute.dev/kyle/goutils/lib"
|
||||||
@@ -21,9 +20,9 @@ func main() {
|
|||||||
|
|
||||||
path := flag.Arg(0)
|
path := flag.Arg(0)
|
||||||
if path == "-" {
|
if path == "-" {
|
||||||
in, err = ioutil.ReadAll(os.Stdin)
|
in, err = io.ReadAll(os.Stdin)
|
||||||
} else {
|
} else {
|
||||||
in, err = ioutil.ReadFile(flag.Arg(0))
|
in, err = os.ReadFile(flag.Arg(0))
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lib.Err(lib.ExitFailure, err, "couldn't read file")
|
lib.Err(lib.ExitFailure, err, "couldn't read file")
|
||||||
@@ -33,5 +32,7 @@ func main() {
|
|||||||
if p == nil {
|
if p == nil {
|
||||||
lib.Errx(lib.ExitFailure, "%s isn't a PEM-encoded file", flag.Arg(0))
|
lib.Errx(lib.ExitFailure, "%s isn't a PEM-encoded file", flag.Arg(0))
|
||||||
}
|
}
|
||||||
fmt.Printf("%s", p.Bytes)
|
if _, err = os.Stdout.Write(p.Bytes); err != nil {
|
||||||
|
lib.Err(lib.ExitFailure, err, "writing body")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@ func main() {
|
|||||||
lib.Err(lib.ExitFailure, err, "failed to read input")
|
lib.Err(lib.ExitFailure, err, "failed to read input")
|
||||||
}
|
}
|
||||||
case argc > 1:
|
case argc > 1:
|
||||||
for i := 0; i < argc; i++ {
|
for i := range argc {
|
||||||
path := flag.Arg(i)
|
path := flag.Arg(i)
|
||||||
err = copyFile(path, buf)
|
err = copyFile(path, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,14 +12,14 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
for _, fileName := range flag.Args() {
|
for _, fileName := range flag.Args() {
|
||||||
data, err := ioutil.ReadFile(fileName)
|
data, err := os.ReadFile(fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "[!] %s: %v\n", fileName, err)
|
fmt.Fprintf(os.Stderr, "[!] %s: %v\n", fileName, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("[+] %s:\n", fileName)
|
fmt.Fprintf(os.Stdout, "[+] %s:\n", fileName)
|
||||||
rest := data[:]
|
rest := data
|
||||||
for {
|
for {
|
||||||
var p *pem.Block
|
var p *pem.Block
|
||||||
p, rest = pem.Decode(rest)
|
p, rest = pem.Decode(rest)
|
||||||
@@ -28,13 +27,14 @@ func main() {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
cert, err := x509.ParseCertificate(p.Bytes)
|
var cert *x509.Certificate
|
||||||
|
cert, err = x509.ParseCertificate(p.Bytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "[!] %s: %v\n", fileName, err)
|
fmt.Fprintf(os.Stderr, "[!] %s: %v\n", fileName, err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\t%+v\n", cert.Subject.CommonName)
|
fmt.Fprintf(os.Stdout, "\t%+v\n", cert.Subject.CommonName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ func newName(path string) (string, error) {
|
|||||||
return hashName(path, encodedHash), nil
|
return hashName(path, encodedHash), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func move(dst, src string, force bool) (err error) {
|
func move(dst, src string, force bool) error {
|
||||||
if fileutil.FileDoesExist(dst) && !force {
|
if fileutil.FileDoesExist(dst) && !force {
|
||||||
return fmt.Errorf("%s exists (pass the -f flag to overwrite)", dst)
|
return fmt.Errorf("%s exists (pass the -f flag to overwrite)", dst)
|
||||||
}
|
}
|
||||||
@@ -52,21 +52,23 @@ func move(dst, src string, force bool) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func(e error) {
|
var retErr error
|
||||||
|
defer func(e *error) {
|
||||||
dstFile.Close()
|
dstFile.Close()
|
||||||
if e != nil {
|
if *e != nil {
|
||||||
os.Remove(dst)
|
os.Remove(dst)
|
||||||
}
|
}
|
||||||
}(err)
|
}(&retErr)
|
||||||
|
|
||||||
srcFile, err := os.Open(src)
|
srcFile, err := os.Open(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
retErr = err
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer srcFile.Close()
|
defer srcFile.Close()
|
||||||
|
|
||||||
_, err = io.Copy(dstFile, srcFile)
|
if _, err = io.Copy(dstFile, srcFile); err != nil {
|
||||||
if err != nil {
|
retErr = err
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +96,44 @@ func init() {
|
|||||||
flag.Usage = func() { usage(os.Stdout) }
|
flag.Usage = func() { usage(os.Stdout) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type options struct {
|
||||||
|
dryRun, force, printChanged, verbose bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func processOne(file string, opt options) error {
|
||||||
|
renamed, err := newName(file)
|
||||||
|
if err != nil {
|
||||||
|
_, _ = lib.Warn(err, "failed to get new file name")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if opt.verbose && !opt.printChanged {
|
||||||
|
fmt.Fprintln(os.Stdout, file)
|
||||||
|
}
|
||||||
|
if renamed == file {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !opt.dryRun {
|
||||||
|
if err = move(renamed, file, opt.force); err != nil {
|
||||||
|
_, _ = lib.Warn(err, "failed to rename file from %s to %s", file, renamed)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if opt.printChanged && !opt.verbose {
|
||||||
|
fmt.Fprintln(os.Stdout, file, "->", renamed)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(dryRun, force, printChanged, verbose bool, files []string) {
|
||||||
|
if verbose && printChanged {
|
||||||
|
printChanged = false
|
||||||
|
}
|
||||||
|
opt := options{dryRun: dryRun, force: force, printChanged: printChanged, verbose: verbose}
|
||||||
|
for _, file := range files {
|
||||||
|
_ = processOne(file, opt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var dryRun, force, printChanged, verbose bool
|
var dryRun, force, printChanged, verbose bool
|
||||||
flag.BoolVar(&force, "f", false, "force overwriting of files if there is a collision")
|
flag.BoolVar(&force, "f", false, "force overwriting of files if there is a collision")
|
||||||
@@ -102,34 +142,5 @@ func main() {
|
|||||||
flag.BoolVar(&verbose, "v", false, "list all processed files")
|
flag.BoolVar(&verbose, "v", false, "list all processed files")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
run(dryRun, force, printChanged, verbose, flag.Args())
|
||||||
if verbose && printChanged {
|
|
||||||
printChanged = false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range flag.Args() {
|
|
||||||
renamed, err := newName(file)
|
|
||||||
if err != nil {
|
|
||||||
lib.Warn(err, "failed to get new file name")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if verbose && !printChanged {
|
|
||||||
fmt.Println(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
if renamed != file {
|
|
||||||
if !dryRun {
|
|
||||||
err = move(renamed, file, force)
|
|
||||||
if err != nil {
|
|
||||||
lib.Warn(err, "failed to rename file from %s to %s", file, renamed)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if printChanged && !verbose {
|
|
||||||
fmt.Println(file, "->", renamed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -66,24 +67,25 @@ func main() {
|
|||||||
for _, remote := range flag.Args() {
|
for _, remote := range flag.Args() {
|
||||||
u, err := url.Parse(remote)
|
u, err := url.Parse(remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lib.Warn(err, "parsing %s", remote)
|
_, _ = lib.Warn(err, "parsing %s", remote)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
name := filepath.Base(u.Path)
|
name := filepath.Base(u.Path)
|
||||||
if name == "" {
|
if name == "" {
|
||||||
lib.Warnx("source URL doesn't appear to name a file")
|
_, _ = lib.Warnx("source URL doesn't appear to name a file")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := http.Get(remote)
|
req, reqErr := http.NewRequestWithContext(context.Background(), http.MethodGet, remote, nil)
|
||||||
if err != nil {
|
if reqErr != nil {
|
||||||
lib.Warn(err, "fetching %s", remote)
|
_, _ = lib.Warn(reqErr, "building request for %s", remote)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lib.Warn(err, "fetching %s", remote)
|
_, _ = lib.Warn(err, "fetching %s", remote)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -17,8 +17,8 @@ func rollDie(count, sides int) []int {
|
|||||||
sum := 0
|
sum := 0
|
||||||
var rolls []int
|
var rolls []int
|
||||||
|
|
||||||
for i := 0; i < count; i++ {
|
for range count {
|
||||||
roll := rand.Intn(sides) + 1
|
roll := rand.IntN(sides) + 1 // #nosec G404
|
||||||
sum += roll
|
sum += roll
|
||||||
rolls = append(rolls, roll)
|
rolls = append(rolls, roll)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func init() {
|
|||||||
project = wd[len(gopath):]
|
project = wd[len(gopath):]
|
||||||
}
|
}
|
||||||
|
|
||||||
func walkFile(path string, info os.FileInfo, err error) error {
|
func walkFile(path string, _ os.FileInfo, err error) error {
|
||||||
if ignores[path] {
|
if ignores[path] {
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
}
|
}
|
||||||
@@ -62,22 +62,27 @@ func walkFile(path string, info os.FileInfo, err error) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
debug.Println(path)
|
|
||||||
|
|
||||||
f, err := parser.ParseFile(fset, path, nil, parser.ImportsOnly)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug.Println(path)
|
||||||
|
|
||||||
|
f, err2 := parser.ParseFile(fset, path, nil, parser.ImportsOnly)
|
||||||
|
if err2 != nil {
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
|
||||||
for _, importSpec := range f.Imports {
|
for _, importSpec := range f.Imports {
|
||||||
importPath := strings.Trim(importSpec.Path.Value, `"`)
|
importPath := strings.Trim(importSpec.Path.Value, `"`)
|
||||||
if stdLibRegexp.MatchString(importPath) {
|
switch {
|
||||||
|
case stdLibRegexp.MatchString(importPath):
|
||||||
debug.Println("standard lib:", importPath)
|
debug.Println("standard lib:", importPath)
|
||||||
continue
|
continue
|
||||||
} else if strings.HasPrefix(importPath, project) {
|
case strings.HasPrefix(importPath, project):
|
||||||
debug.Println("internal import:", importPath)
|
debug.Println("internal import:", importPath)
|
||||||
continue
|
continue
|
||||||
} else if strings.HasPrefix(importPath, "golang.org/") {
|
case strings.HasPrefix(importPath, "golang.org/"):
|
||||||
debug.Println("extended lib:", importPath)
|
debug.Println("extended lib:", importPath)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -102,7 +107,7 @@ func main() {
|
|||||||
ignores["vendor"] = true
|
ignores["vendor"] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, word := range strings.Split(ignoreLine, ",") {
|
for word := range strings.SplitSeq(ignoreLine, ",") {
|
||||||
ignores[strings.TrimSpace(word)] = true
|
ignores[strings.TrimSpace(word)] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"crypto"
|
"crypto"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/sha1"
|
"crypto/sha1" // #nosec G505
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/asn1"
|
"encoding/asn1"
|
||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -21,6 +20,11 @@ import (
|
|||||||
"git.wntrmute.dev/kyle/goutils/lib"
|
"git.wntrmute.dev/kyle/goutils/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
keyTypeRSA = "RSA"
|
||||||
|
keyTypeECDSA = "ECDSA"
|
||||||
|
)
|
||||||
|
|
||||||
func usage(w io.Writer) {
|
func usage(w io.Writer) {
|
||||||
fmt.Fprintf(w, `ski: print subject key info for PEM-encoded files
|
fmt.Fprintf(w, `ski: print subject key info for PEM-encoded files
|
||||||
|
|
||||||
@@ -39,14 +43,14 @@ func init() {
|
|||||||
flag.Usage = func() { usage(os.Stderr) }
|
flag.Usage = func() { usage(os.Stderr) }
|
||||||
}
|
}
|
||||||
|
|
||||||
func parse(path string) (public []byte, kt, ft string) {
|
func parse(path string) ([]byte, string, string) {
|
||||||
data, err := ioutil.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
die.If(err)
|
die.If(err)
|
||||||
|
|
||||||
data = bytes.TrimSpace(data)
|
data = bytes.TrimSpace(data)
|
||||||
p, rest := pem.Decode(data)
|
p, rest := pem.Decode(data)
|
||||||
if len(rest) > 0 {
|
if len(rest) > 0 {
|
||||||
lib.Warnx("trailing data in PEM file")
|
_, _ = lib.Warnx("trailing data in PEM file")
|
||||||
}
|
}
|
||||||
|
|
||||||
if p == nil {
|
if p == nil {
|
||||||
@@ -55,6 +59,12 @@ func parse(path string) (public []byte, kt, ft string) {
|
|||||||
|
|
||||||
data = p.Bytes
|
data = p.Bytes
|
||||||
|
|
||||||
|
var (
|
||||||
|
public []byte
|
||||||
|
kt string
|
||||||
|
ft string
|
||||||
|
)
|
||||||
|
|
||||||
switch p.Type {
|
switch p.Type {
|
||||||
case "PRIVATE KEY", "RSA PRIVATE KEY", "EC PRIVATE KEY":
|
case "PRIVATE KEY", "RSA PRIVATE KEY", "EC PRIVATE KEY":
|
||||||
public, kt = parseKey(data)
|
public, kt = parseKey(data)
|
||||||
@@ -69,10 +79,10 @@ func parse(path string) (public []byte, kt, ft string) {
|
|||||||
die.With("unknown PEM type %s", p.Type)
|
die.With("unknown PEM type %s", p.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return public, kt, ft
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseKey(data []byte) (public []byte, kt string) {
|
func parseKey(data []byte) ([]byte, string) {
|
||||||
privInterface, err := x509.ParsePKCS8PrivateKey(data)
|
privInterface, err := x509.ParsePKCS8PrivateKey(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
privInterface, err = x509.ParsePKCS1PrivateKey(data)
|
privInterface, err = x509.ParsePKCS1PrivateKey(data)
|
||||||
@@ -85,66 +95,71 @@ func parseKey(data []byte) (public []byte, kt string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var priv crypto.Signer
|
var priv crypto.Signer
|
||||||
switch privInterface.(type) {
|
var kt string
|
||||||
|
switch p := privInterface.(type) {
|
||||||
case *rsa.PrivateKey:
|
case *rsa.PrivateKey:
|
||||||
priv = privInterface.(*rsa.PrivateKey)
|
priv = p
|
||||||
kt = "RSA"
|
kt = keyTypeRSA
|
||||||
case *ecdsa.PrivateKey:
|
case *ecdsa.PrivateKey:
|
||||||
priv = privInterface.(*ecdsa.PrivateKey)
|
priv = p
|
||||||
kt = "ECDSA"
|
kt = keyTypeECDSA
|
||||||
default:
|
default:
|
||||||
die.With("unknown private key type %T", privInterface)
|
die.With("unknown private key type %T", privInterface)
|
||||||
}
|
}
|
||||||
|
|
||||||
public, err = x509.MarshalPKIXPublicKey(priv.Public())
|
public, err := x509.MarshalPKIXPublicKey(priv.Public())
|
||||||
die.If(err)
|
die.If(err)
|
||||||
|
|
||||||
return
|
return public, kt
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseCertificate(data []byte) (public []byte, kt string) {
|
func parseCertificate(data []byte) ([]byte, string) {
|
||||||
cert, err := x509.ParseCertificate(data)
|
cert, err := x509.ParseCertificate(data)
|
||||||
die.If(err)
|
die.If(err)
|
||||||
|
|
||||||
pub := cert.PublicKey
|
pub := cert.PublicKey
|
||||||
|
var kt string
|
||||||
switch pub.(type) {
|
switch pub.(type) {
|
||||||
case *rsa.PublicKey:
|
case *rsa.PublicKey:
|
||||||
kt = "RSA"
|
kt = keyTypeRSA
|
||||||
case *ecdsa.PublicKey:
|
case *ecdsa.PublicKey:
|
||||||
kt = "ECDSA"
|
kt = keyTypeECDSA
|
||||||
default:
|
default:
|
||||||
die.With("unknown public key type %T", pub)
|
die.With("unknown public key type %T", pub)
|
||||||
}
|
}
|
||||||
|
|
||||||
public, err = x509.MarshalPKIXPublicKey(pub)
|
public, err := x509.MarshalPKIXPublicKey(pub)
|
||||||
die.If(err)
|
die.If(err)
|
||||||
return
|
return public, kt
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseCSR(data []byte) (public []byte, kt string) {
|
func parseCSR(data []byte) ([]byte, string) {
|
||||||
csr, err := x509.ParseCertificateRequest(data)
|
csr, err := x509.ParseCertificateRequest(data)
|
||||||
die.If(err)
|
die.If(err)
|
||||||
|
|
||||||
pub := csr.PublicKey
|
pub := csr.PublicKey
|
||||||
|
var kt string
|
||||||
switch pub.(type) {
|
switch pub.(type) {
|
||||||
case *rsa.PublicKey:
|
case *rsa.PublicKey:
|
||||||
kt = "RSA"
|
kt = keyTypeRSA
|
||||||
case *ecdsa.PublicKey:
|
case *ecdsa.PublicKey:
|
||||||
kt = "ECDSA"
|
kt = keyTypeECDSA
|
||||||
default:
|
default:
|
||||||
die.With("unknown public key type %T", pub)
|
die.With("unknown public key type %T", pub)
|
||||||
}
|
}
|
||||||
|
|
||||||
public, err = x509.MarshalPKIXPublicKey(pub)
|
public, err := x509.MarshalPKIXPublicKey(pub)
|
||||||
die.If(err)
|
die.If(err)
|
||||||
return
|
return public, kt
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpHex(in []byte) string {
|
func dumpHex(in []byte) string {
|
||||||
var s string
|
var s string
|
||||||
|
var sSb153 strings.Builder
|
||||||
for i := range in {
|
for i := range in {
|
||||||
s += fmt.Sprintf("%02X:", in[i])
|
sSb153.WriteString(fmt.Sprintf("%02X:", in[i]))
|
||||||
}
|
}
|
||||||
|
s += sSb153.String()
|
||||||
|
|
||||||
return strings.Trim(s, ":")
|
return strings.Trim(s, ":")
|
||||||
}
|
}
|
||||||
@@ -172,18 +187,18 @@ func main() {
|
|||||||
var subPKI subjectPublicKeyInfo
|
var subPKI subjectPublicKeyInfo
|
||||||
_, err := asn1.Unmarshal(public, &subPKI)
|
_, err := asn1.Unmarshal(public, &subPKI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lib.Warn(err, "failed to get subject PKI")
|
_, _ = lib.Warn(err, "failed to get subject PKI")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
pubHash := sha1.Sum(subPKI.SubjectPublicKey.Bytes)
|
pubHash := sha1.Sum(subPKI.SubjectPublicKey.Bytes) // #nosec G401 this is the standard
|
||||||
pubHashString := dumpHex(pubHash[:])
|
pubHashString := dumpHex(pubHash[:])
|
||||||
if ski == "" {
|
if ski == "" {
|
||||||
ski = pubHashString
|
ski = pubHashString
|
||||||
}
|
}
|
||||||
|
|
||||||
if shouldMatch && ski != pubHashString {
|
if shouldMatch && ski != pubHashString {
|
||||||
lib.Warnx("%s: SKI mismatch (%s != %s)",
|
_, _ = lib.Warnx("%s: SKI mismatch (%s != %s)",
|
||||||
path, ski, pubHashString)
|
path, ski, pubHashString)
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s (%s %s)\n", path, pubHashString, kt, ft)
|
fmt.Printf("%s %s (%s %s)\n", path, pubHashString, kt, ft)
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"git.wntrmute.dev/kyle/goutils/die"
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
|
"git.wntrmute.dev/kyle/goutils/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
func proxy(conn net.Conn, inside string) error {
|
func proxy(conn net.Conn, inside string) error {
|
||||||
proxyConn, err := net.Dial("tcp", inside)
|
proxyConn, err := (&net.Dialer{}).DialContext(context.Background(), "tcp", inside)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -19,7 +20,7 @@ func proxy(conn net.Conn, inside string) error {
|
|||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
io.Copy(conn, proxyConn)
|
_, _ = io.Copy(conn, proxyConn)
|
||||||
}()
|
}()
|
||||||
_, err = io.Copy(proxyConn, conn)
|
_, err = io.Copy(proxyConn, conn)
|
||||||
return err
|
return err
|
||||||
@@ -31,16 +32,22 @@ func main() {
|
|||||||
flag.StringVar(&inside, "p", "4000", "inside port")
|
flag.StringVar(&inside, "p", "4000", "inside port")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
l, err := net.Listen("tcp", "0.0.0.0:"+outside)
|
lc := &net.ListenConfig{}
|
||||||
|
l, err := lc.Listen(context.Background(), "tcp", "0.0.0.0:"+outside)
|
||||||
die.If(err)
|
die.If(err)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
conn, err := l.Accept()
|
var conn net.Conn
|
||||||
|
conn, err = l.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
_, _ = lib.Warn(err, "accept failed")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
go proxy(conn, "127.0.0.1:"+inside)
|
go func() {
|
||||||
|
if err = proxy(conn, "127.0.0.1:"+inside); err != nil {
|
||||||
|
_, _ = lib.Warn(err, "proxy error")
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
@@ -8,7 +9,6 @@ import (
|
|||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cfg := &tls.Config{}
|
cfg := &tls.Config{} // #nosec G402
|
||||||
|
|
||||||
var sysRoot, listenAddr, certFile, keyFile string
|
var sysRoot, listenAddr, certFile, keyFile string
|
||||||
var verify bool
|
var verify bool
|
||||||
@@ -47,7 +47,8 @@ func main() {
|
|||||||
}
|
}
|
||||||
cfg.Certificates = append(cfg.Certificates, cert)
|
cfg.Certificates = append(cfg.Certificates, cert)
|
||||||
if sysRoot != "" {
|
if sysRoot != "" {
|
||||||
pemList, err := ioutil.ReadFile(sysRoot)
|
var pemList []byte
|
||||||
|
pemList, err = os.ReadFile(sysRoot)
|
||||||
die.If(err)
|
die.If(err)
|
||||||
|
|
||||||
roots := x509.NewCertPool()
|
roots := x509.NewCertPool()
|
||||||
@@ -59,48 +60,54 @@ func main() {
|
|||||||
cfg.RootCAs = roots
|
cfg.RootCAs = roots
|
||||||
}
|
}
|
||||||
|
|
||||||
l, err := net.Listen("tcp", listenAddr)
|
lc := &net.ListenConfig{}
|
||||||
|
l, err := lc.Listen(context.Background(), "tcp", listenAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err.Error())
|
fmt.Println(err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
conn, err := l.Accept()
|
var conn net.Conn
|
||||||
|
conn, err = l.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err.Error())
|
fmt.Println(err.Error())
|
||||||
}
|
|
||||||
|
|
||||||
raddr := conn.RemoteAddr()
|
|
||||||
tconn := tls.Server(conn, cfg)
|
|
||||||
err = tconn.Handshake()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("[+] %v: failed to complete handshake: %v\n", raddr, err)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cs := tconn.ConnectionState()
|
handleConn(conn, cfg)
|
||||||
if len(cs.PeerCertificates) == 0 {
|
|
||||||
fmt.Printf("[+] %v: no chain presented\n", raddr)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var chain []byte
|
|
||||||
for _, cert := range cs.PeerCertificates {
|
|
||||||
p := &pem.Block{
|
|
||||||
Type: "CERTIFICATE",
|
|
||||||
Bytes: cert.Raw,
|
|
||||||
}
|
|
||||||
chain = append(chain, pem.EncodeToMemory(p)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
var nonce [16]byte
|
|
||||||
_, err = rand.Read(nonce[:])
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fname := fmt.Sprintf("%v-%v.pem", raddr, hex.EncodeToString(nonce[:]))
|
|
||||||
err = ioutil.WriteFile(fname, chain, 0644)
|
|
||||||
die.If(err)
|
|
||||||
fmt.Printf("%v: [+] wrote %v.\n", raddr, fname)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleConn performs a TLS handshake, extracts the peer chain, and writes it to a file.
|
||||||
|
func handleConn(conn net.Conn, cfg *tls.Config) {
|
||||||
|
defer conn.Close()
|
||||||
|
raddr := conn.RemoteAddr()
|
||||||
|
tconn := tls.Server(conn, cfg)
|
||||||
|
if err := tconn.HandshakeContext(context.Background()); err != nil {
|
||||||
|
fmt.Printf("[+] %v: failed to complete handshake: %v\n", raddr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cs := tconn.ConnectionState()
|
||||||
|
if len(cs.PeerCertificates) == 0 {
|
||||||
|
fmt.Printf("[+] %v: no chain presented\n", raddr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var chain []byte
|
||||||
|
for _, cert := range cs.PeerCertificates {
|
||||||
|
p := &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
|
||||||
|
chain = append(chain, pem.EncodeToMemory(p)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var nonce [16]byte
|
||||||
|
if _, err := rand.Read(nonce[:]); err != nil {
|
||||||
|
fmt.Printf("[+] %v: failed to generate filename nonce: %v\n", raddr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fname := fmt.Sprintf("%v-%v.pem", raddr, hex.EncodeToString(nonce[:]))
|
||||||
|
if err := os.WriteFile(fname, chain, 0o644); err != nil {
|
||||||
|
fmt.Printf("[+] %v: failed to write %v: %v\n", raddr, fname, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("%v: [+] wrote %v.\n", raddr, fname)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var cfg = &tls.Config{}
|
var cfg = &tls.Config{} // #nosec G402
|
||||||
|
|
||||||
var sysRoot, serverName string
|
var sysRoot, serverName string
|
||||||
flag.StringVar(&sysRoot, "ca", "", "provide an alternate CA bundle")
|
flag.StringVar(&sysRoot, "ca", "", "provide an alternate CA bundle")
|
||||||
@@ -23,7 +23,7 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if sysRoot != "" {
|
if sysRoot != "" {
|
||||||
pemList, err := ioutil.ReadFile(sysRoot)
|
pemList, err := os.ReadFile(sysRoot)
|
||||||
die.If(err)
|
die.If(err)
|
||||||
|
|
||||||
roots := x509.NewCertPool()
|
roots := x509.NewCertPool()
|
||||||
@@ -44,10 +44,13 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
site += ":443"
|
site += ":443"
|
||||||
}
|
}
|
||||||
conn, err := tls.Dial("tcp", site, cfg)
|
d := &tls.Dialer{Config: cfg}
|
||||||
if err != nil {
|
nc, err := d.DialContext(context.Background(), "tcp", site)
|
||||||
fmt.Println(err.Error())
|
die.If(err)
|
||||||
os.Exit(1)
|
|
||||||
|
conn, ok := nc.(*tls.Conn)
|
||||||
|
if !ok {
|
||||||
|
die.With("invalid TLS connection (not a *tls.Conn)")
|
||||||
}
|
}
|
||||||
|
|
||||||
cs := conn.ConnectionState()
|
cs := conn.ConnectionState()
|
||||||
@@ -61,8 +64,9 @@ func main() {
|
|||||||
chain = append(chain, pem.EncodeToMemory(p)...)
|
chain = append(chain, pem.EncodeToMemory(p)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ioutil.WriteFile(site+".pem", chain, 0644)
|
err = os.WriteFile(site+".pem", chain, 0644)
|
||||||
die.If(err)
|
die.If(err)
|
||||||
|
|
||||||
fmt.Printf("[+] wrote %s.pem.\n", site)
|
fmt.Printf("[+] wrote %s.pem.\n", site)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ func printDigests(paths []string, issuer bool) {
|
|||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
cert, err := certlib.LoadCertificate(path)
|
cert, err := certlib.LoadCertificate(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lib.Warn(err, "failed to load certificate from %s", path)
|
_, _ = lib.Warn(err, "failed to load certificate from %s", path)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,20 +75,19 @@ func matchDigests(paths []string, issuer bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var invalid int
|
var invalid int
|
||||||
for {
|
for len(paths) > 0 {
|
||||||
if len(paths) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
fst := paths[0]
|
fst := paths[0]
|
||||||
snd := paths[1]
|
snd := paths[1]
|
||||||
paths = paths[2:]
|
paths = paths[2:]
|
||||||
|
|
||||||
fstCert, err := certlib.LoadCertificate(fst)
|
fstCert, err := certlib.LoadCertificate(fst)
|
||||||
die.If(err)
|
die.If(err)
|
||||||
|
|
||||||
sndCert, err := certlib.LoadCertificate(snd)
|
sndCert, err := certlib.LoadCertificate(snd)
|
||||||
die.If(err)
|
die.If(err)
|
||||||
|
|
||||||
if !bytes.Equal(getSubjectInfoHash(fstCert, issuer), getSubjectInfoHash(sndCert, issuer)) {
|
if !bytes.Equal(getSubjectInfoHash(fstCert, issuer), getSubjectInfoHash(sndCert, issuer)) {
|
||||||
lib.Warnx("certificates don't match: %s and %s", fst, snd)
|
_, _ = lib.Warnx("certificates don't match: %s and %s", fst, snd)
|
||||||
invalid++
|
invalid++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/goutils/certlib/hosts"
|
||||||
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -13,16 +17,23 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
hostPort := os.Args[1]
|
hostPort, err := hosts.ParseHost(os.Args[1])
|
||||||
conn, err := tls.Dial("tcp", hostPort, &tls.Config{
|
die.If(err)
|
||||||
InsecureSkipVerify: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
d := &tls.Dialer{Config: &tls.Config{
|
||||||
fmt.Printf("Failed to connect to the TLS server: %v\n", err)
|
InsecureSkipVerify: true,
|
||||||
os.Exit(1)
|
}} // #nosec G402
|
||||||
|
|
||||||
|
nc, err := d.DialContext(context.Background(), "tcp", hostPort.String())
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
conn, ok := nc.(*tls.Conn)
|
||||||
|
if !ok {
|
||||||
|
die.With("invalid TLS connection (not a *tls.Conn)")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
state := conn.ConnectionState()
|
state := conn.ConnectionState()
|
||||||
printConnectionDetails(state)
|
printConnectionDetails(state)
|
||||||
}
|
}
|
||||||
@@ -37,7 +48,6 @@ func printConnectionDetails(state tls.ConnectionState) {
|
|||||||
|
|
||||||
func tlsVersion(version uint16) string {
|
func tlsVersion(version uint16) string {
|
||||||
switch version {
|
switch version {
|
||||||
|
|
||||||
case tls.VersionTLS13:
|
case tls.VersionTLS13:
|
||||||
return "TLS 1.3"
|
return "TLS 1.3"
|
||||||
case tls.VersionTLS12:
|
case tls.VersionTLS12:
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.wntrmute.dev/kyle/goutils/die"
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
@@ -32,7 +30,7 @@ const (
|
|||||||
curveP521
|
curveP521
|
||||||
)
|
)
|
||||||
|
|
||||||
func getECCurve(pub interface{}) int {
|
func getECCurve(pub any) int {
|
||||||
switch pub := pub.(type) {
|
switch pub := pub.(type) {
|
||||||
case *ecdsa.PublicKey:
|
case *ecdsa.PublicKey:
|
||||||
switch pub.Curve {
|
switch pub.Curve {
|
||||||
@@ -52,8 +50,75 @@ func getECCurve(pub interface{}) int {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// matchRSA compares an RSA public key from certificate against RSA public key from private key.
|
||||||
|
// It returns true on match.
|
||||||
|
func matchRSA(certPub *rsa.PublicKey, keyPub *rsa.PublicKey) bool {
|
||||||
|
return keyPub.N.Cmp(certPub.N) == 0 && keyPub.E == certPub.E
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchECDSA compares ECDSA public keys for equality and compatible curve.
|
||||||
|
// It returns match=true when they are on the same curve and have the same X/Y.
|
||||||
|
// If curves mismatch, match is false.
|
||||||
|
func matchECDSA(certPub *ecdsa.PublicKey, keyPub *ecdsa.PublicKey) bool {
|
||||||
|
if getECCurve(certPub) != getECCurve(keyPub) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if keyPub.X.Cmp(certPub.X) != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if keyPub.Y.Cmp(certPub.Y) != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchKeys determines whether the certificate's public key matches the given private key.
|
||||||
|
// It returns true if they match; otherwise, it returns false and a human-friendly reason.
|
||||||
|
func matchKeys(cert *x509.Certificate, priv crypto.Signer) (bool, string) {
|
||||||
|
switch keyPub := priv.Public().(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
switch certPub := cert.PublicKey.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
if matchRSA(certPub, keyPub) {
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
return false, "public keys don't match"
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
return false, "RSA private key, EC public key"
|
||||||
|
default:
|
||||||
|
return false, fmt.Sprintf("unsupported certificate public key type: %T", cert.PublicKey)
|
||||||
|
}
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
switch certPub := cert.PublicKey.(type) {
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
if matchECDSA(certPub, keyPub) {
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
// Determine a more precise reason
|
||||||
|
kc := getECCurve(keyPub)
|
||||||
|
cc := getECCurve(certPub)
|
||||||
|
if kc == curveInvalid {
|
||||||
|
return false, "invalid private key curve"
|
||||||
|
}
|
||||||
|
if cc == curveRSA {
|
||||||
|
return false, "private key is EC, certificate is RSA"
|
||||||
|
}
|
||||||
|
if kc != cc {
|
||||||
|
return false, "EC curves don't match"
|
||||||
|
}
|
||||||
|
return false, "public keys don't match"
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
return false, "private key is EC, certificate is RSA"
|
||||||
|
default:
|
||||||
|
return false, fmt.Sprintf("unsupported certificate public key type: %T", cert.PublicKey)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false, fmt.Sprintf("unrecognised private key type: %T", priv.Public())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func loadKey(path string) (crypto.Signer, error) {
|
func loadKey(path string) (crypto.Signer, error) {
|
||||||
in, err := ioutil.ReadFile(path)
|
in, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -78,16 +143,15 @@ func loadKey(path string) (crypto.Signer, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch priv.(type) {
|
switch p := priv.(type) {
|
||||||
case *rsa.PrivateKey:
|
case *rsa.PrivateKey:
|
||||||
return priv.(*rsa.PrivateKey), nil
|
return p, nil
|
||||||
case *ecdsa.PrivateKey:
|
case *ecdsa.PrivateKey:
|
||||||
return priv.(*ecdsa.PrivateKey), nil
|
return p, nil
|
||||||
|
default:
|
||||||
|
// should never reach here
|
||||||
|
return nil, errors.New("invalid private key")
|
||||||
}
|
}
|
||||||
|
|
||||||
// should never reach here
|
|
||||||
return nil, errors.New("invalid private key")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -96,7 +160,7 @@ func main() {
|
|||||||
flag.StringVar(&certFile, "c", "", "TLS `certificate` file")
|
flag.StringVar(&certFile, "c", "", "TLS `certificate` file")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
in, err := ioutil.ReadFile(certFile)
|
in, err := os.ReadFile(certFile)
|
||||||
die.If(err)
|
die.If(err)
|
||||||
|
|
||||||
p, _ := pem.Decode(in)
|
p, _ := pem.Decode(in)
|
||||||
@@ -112,50 +176,11 @@ func main() {
|
|||||||
priv, err := loadKey(keyFile)
|
priv, err := loadKey(keyFile)
|
||||||
die.If(err)
|
die.If(err)
|
||||||
|
|
||||||
switch pub := priv.Public().(type) {
|
matched, reason := matchKeys(cert, priv)
|
||||||
case *rsa.PublicKey:
|
if matched {
|
||||||
switch certPub := cert.PublicKey.(type) {
|
|
||||||
case *rsa.PublicKey:
|
|
||||||
if pub.N.Cmp(certPub.N) != 0 || pub.E != certPub.E {
|
|
||||||
fmt.Println("No match (public keys don't match).")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Println("Match.")
|
|
||||||
return
|
|
||||||
case *ecdsa.PublicKey:
|
|
||||||
fmt.Println("No match (RSA private key, EC public key).")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
case *ecdsa.PublicKey:
|
|
||||||
privCurve := getECCurve(pub)
|
|
||||||
certCurve := getECCurve(cert.PublicKey)
|
|
||||||
log.Printf("priv: %d\tcert: %d\n", privCurve, certCurve)
|
|
||||||
|
|
||||||
if certCurve == curveRSA {
|
|
||||||
fmt.Println("No match (private key is EC, certificate is RSA).")
|
|
||||||
os.Exit(1)
|
|
||||||
} else if privCurve == curveInvalid {
|
|
||||||
fmt.Println("No match (invalid private key curve).")
|
|
||||||
os.Exit(1)
|
|
||||||
} else if privCurve != certCurve {
|
|
||||||
fmt.Println("No match (EC curves don't match).")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
certPub := cert.PublicKey.(*ecdsa.PublicKey)
|
|
||||||
if pub.X.Cmp(certPub.X) != 0 {
|
|
||||||
fmt.Println("No match (public keys don't match).")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if pub.Y.Cmp(certPub.Y) != 0 {
|
|
||||||
fmt.Println("No match (public keys don't match).")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Match.")
|
fmt.Println("Match.")
|
||||||
default:
|
return
|
||||||
fmt.Printf("Unrecognised private key type: %T\n", priv.Public())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
fmt.Printf("No match (%s).\n", reason)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,10 +201,6 @@ func init() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if fromLoc == time.UTC {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
toLoc = time.UTC
|
toLoc = time.UTC
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,15 +253,16 @@ func main() {
|
|||||||
showTime(time.Now())
|
showTime(time.Now())
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
case 1:
|
case 1:
|
||||||
if flag.Arg(0) == "-" {
|
switch {
|
||||||
|
case flag.Arg(0) == "-":
|
||||||
s := bufio.NewScanner(os.Stdin)
|
s := bufio.NewScanner(os.Stdin)
|
||||||
|
|
||||||
for s.Scan() {
|
for s.Scan() {
|
||||||
times = append(times, s.Text())
|
times = append(times, s.Text())
|
||||||
}
|
}
|
||||||
} else if flag.Arg(0) == "help" {
|
case flag.Arg(0) == "help":
|
||||||
usageExamples()
|
usageExamples()
|
||||||
} else {
|
default:
|
||||||
times = flag.Args()
|
times = flag.Args()
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
@@ -12,9 +11,8 @@ import (
|
|||||||
|
|
||||||
type empty struct{}
|
type empty struct{}
|
||||||
|
|
||||||
func errorf(format string, args ...interface{}) {
|
func errorf(path string, err error) {
|
||||||
format += "\n"
|
fmt.Fprintf(os.Stderr, "%s FAILED: %s\n", path, err)
|
||||||
fmt.Fprintf(os.Stderr, format, args...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func usage(w io.Writer) {
|
func usage(w io.Writer) {
|
||||||
@@ -44,16 +42,16 @@ func main() {
|
|||||||
|
|
||||||
if flag.NArg() == 1 && flag.Arg(0) == "-" {
|
if flag.NArg() == 1 && flag.Arg(0) == "-" {
|
||||||
path := "stdin"
|
path := "stdin"
|
||||||
in, err := ioutil.ReadAll(os.Stdin)
|
in, err := io.ReadAll(os.Stdin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorf("%s FAILED: %s", path, err)
|
errorf(path, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var e empty
|
var e empty
|
||||||
err = yaml.Unmarshal(in, &e)
|
err = yaml.Unmarshal(in, &e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorf("%s FAILED: %s", path, err)
|
errorf(path, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,16 +63,16 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, path := range flag.Args() {
|
for _, path := range flag.Args() {
|
||||||
in, err := ioutil.ReadFile(path)
|
in, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorf("%s FAILED: %s", path, err)
|
errorf(path, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var e empty
|
var e empty
|
||||||
err = yaml.Unmarshal(in, &e)
|
err = yaml.Unmarshal(in, &e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorf("%s FAILED: %s", path, err)
|
errorf(path, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,16 +14,16 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/goutils/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultDirectory = ".git/objects"
|
const defaultDirectory = ".git/objects"
|
||||||
|
|
||||||
func errorf(format string, a ...interface{}) {
|
// maxDecompressedSize limits how many bytes we will decompress from a zlib
|
||||||
fmt.Fprintf(os.Stderr, format, a...)
|
// stream to mitigate decompression bombs (gosec G110).
|
||||||
if format[len(format)-1] != '\n' {
|
// Increase this if you expect larger objects.
|
||||||
fmt.Fprintf(os.Stderr, "\n")
|
const maxDecompressedSize int64 = 64 << 30 // 64 GiB
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isDir(path string) bool {
|
func isDir(path string) bool {
|
||||||
fi, err := os.Stat(path)
|
fi, err := os.Stat(path)
|
||||||
@@ -48,17 +48,21 @@ func loadFile(path string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
defer zread.Close()
|
defer zread.Close()
|
||||||
|
|
||||||
_, err = io.Copy(buf, zread)
|
// Protect against decompression bombs by limiting how much we read.
|
||||||
if err != nil {
|
lr := io.LimitReader(zread, maxDecompressedSize+1)
|
||||||
|
if _, err = buf.ReadFrom(lr); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if int64(buf.Len()) > maxDecompressedSize {
|
||||||
|
return nil, fmt.Errorf("decompressed size exceeds limit (%d bytes)", maxDecompressedSize)
|
||||||
|
}
|
||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func showFile(path string) {
|
func showFile(path string) {
|
||||||
fileData, err := loadFile(path)
|
fileData, err := loadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorf("%v", err)
|
lib.Warn(err, "failed to load %s", path)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,39 +72,71 @@ func showFile(path string) {
|
|||||||
func searchFile(path string, search *regexp.Regexp) error {
|
func searchFile(path string, search *regexp.Regexp) error {
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorf("%v", err)
|
lib.Warn(err, "failed to open %s", path)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
zread, err := zlib.NewReader(file)
|
zread, err := zlib.NewReader(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorf("%v", err)
|
lib.Warn(err, "failed to decompress %s", path)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer zread.Close()
|
defer zread.Close()
|
||||||
|
|
||||||
zbuf := bufio.NewReader(zread)
|
// Limit how much we scan to avoid DoS via huge decompression.
|
||||||
if search.MatchReader(zbuf) {
|
lr := io.LimitReader(zread, maxDecompressedSize+1)
|
||||||
fileData, err := loadFile(path)
|
zbuf := bufio.NewReader(lr)
|
||||||
if err != nil {
|
if !search.MatchReader(zbuf) {
|
||||||
errorf("%v", err)
|
return nil
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf("%s:\n%s\n", path, fileData)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileData, err := loadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
lib.Warn(err, "failed to load %s", path)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("%s:\n%s\n", path, fileData)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildWalker(searchExpr *regexp.Regexp) filepath.WalkFunc {
|
func buildWalker(searchExpr *regexp.Regexp) filepath.WalkFunc {
|
||||||
return func(path string, info os.FileInfo, err error) error {
|
return func(path string, info os.FileInfo, _ error) error {
|
||||||
if info.Mode().IsRegular() {
|
if !info.Mode().IsRegular() {
|
||||||
return searchFile(path, searchExpr)
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
return searchFile(path, searchExpr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// runSearch compiles the search expression and processes the provided paths.
|
||||||
|
// It returns an error for fatal conditions; per-file errors are logged.
|
||||||
|
func runSearch(expr string) error {
|
||||||
|
search, err := regexp.Compile(expr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid regexp: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pathList := flag.Args()
|
||||||
|
if len(pathList) == 0 {
|
||||||
|
pathList = []string{defaultDirectory}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, path := range pathList {
|
||||||
|
if isDir(path) {
|
||||||
|
if err2 := filepath.Walk(path, buildWalker(search)); err2 != nil {
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err2 := searchFile(path, search); err2 != nil {
|
||||||
|
// Non-fatal: keep going, but report it.
|
||||||
|
lib.Warn(err2, "non-fatal error while searching files")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flSearch := flag.String("s", "", "search string (should be an RE2 regular expression)")
|
flSearch := flag.String("s", "", "search string (should be an RE2 regular expression)")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
@@ -109,28 +145,10 @@ func main() {
|
|||||||
for _, path := range flag.Args() {
|
for _, path := range flag.Args() {
|
||||||
showFile(path)
|
showFile(path)
|
||||||
}
|
}
|
||||||
} else {
|
return
|
||||||
search, err := regexp.Compile(*flSearch)
|
}
|
||||||
if err != nil {
|
|
||||||
errorf("Bad regexp: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pathList := flag.Args()
|
if err := runSearch(*flSearch); err != nil {
|
||||||
if len(pathList) == 0 {
|
lib.Err(lib.ExitFailure, err, "failed to run search")
|
||||||
pathList = []string{defaultDirectory}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, path := range pathList {
|
|
||||||
if isDir(path) {
|
|
||||||
err := filepath.Walk(path, buildWalker(search))
|
|
||||||
if err != nil {
|
|
||||||
errorf("%v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
searchFile(path, search)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build freebsd darwin,386 netbsd
|
//go:build bsd
|
||||||
|
|
||||||
package lib
|
package lib
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build unix linux openbsd darwin,amd64
|
//go:build unix || linux || openbsd || (darwin && amd64)
|
||||||
|
|
||||||
package lib
|
package lib
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ type FileTime struct {
|
|||||||
|
|
||||||
func timeSpecToTime(ts unix.Timespec) time.Time {
|
func timeSpecToTime(ts unix.Timespec) time.Time {
|
||||||
// The casts to int64 are needed because on 386, these are int32s.
|
// The casts to int64 are needed because on 386, these are int32s.
|
||||||
return time.Unix(int64(ts.Sec), int64(ts.Nsec))
|
return time.Unix(ts.Sec, ts.Nsec)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadFileTime returns a FileTime associated with the file.
|
// LoadFileTime returns a FileTime associated with the file.
|
||||||
|
|||||||
32
lib/lib.go
32
lib/lib.go
@@ -10,6 +10,12 @@ import (
|
|||||||
|
|
||||||
var progname = filepath.Base(os.Args[0])
|
var progname = filepath.Base(os.Args[0])
|
||||||
|
|
||||||
|
const (
|
||||||
|
daysInYear = 365
|
||||||
|
digitWidth = 10
|
||||||
|
hoursInQuarterDay = 6
|
||||||
|
)
|
||||||
|
|
||||||
// ProgName returns what lib thinks the program name is, namely the
|
// ProgName returns what lib thinks the program name is, namely the
|
||||||
// basename of argv0.
|
// basename of argv0.
|
||||||
//
|
//
|
||||||
@@ -20,7 +26,7 @@ func ProgName() string {
|
|||||||
|
|
||||||
// Warnx displays a formatted error message to standard error, à la
|
// Warnx displays a formatted error message to standard error, à la
|
||||||
// warnx(3).
|
// warnx(3).
|
||||||
func Warnx(format string, a ...interface{}) (int, error) {
|
func Warnx(format string, a ...any) (int, error) {
|
||||||
format = fmt.Sprintf("[%s] %s", progname, format)
|
format = fmt.Sprintf("[%s] %s", progname, format)
|
||||||
format += "\n"
|
format += "\n"
|
||||||
return fmt.Fprintf(os.Stderr, format, a...)
|
return fmt.Fprintf(os.Stderr, format, a...)
|
||||||
@@ -28,7 +34,7 @@ func Warnx(format string, a ...interface{}) (int, error) {
|
|||||||
|
|
||||||
// Warn displays a formatted error message to standard output,
|
// Warn displays a formatted error message to standard output,
|
||||||
// appending the error string, à la warn(3).
|
// appending the error string, à la warn(3).
|
||||||
func Warn(err error, format string, a ...interface{}) (int, error) {
|
func Warn(err error, format string, a ...any) (int, error) {
|
||||||
format = fmt.Sprintf("[%s] %s", progname, format)
|
format = fmt.Sprintf("[%s] %s", progname, format)
|
||||||
format += ": %v\n"
|
format += ": %v\n"
|
||||||
a = append(a, err)
|
a = append(a, err)
|
||||||
@@ -37,7 +43,7 @@ func Warn(err error, format string, a ...interface{}) (int, error) {
|
|||||||
|
|
||||||
// Errx displays a formatted error message to standard error and exits
|
// Errx displays a formatted error message to standard error and exits
|
||||||
// with the status code from `exit`, à la errx(3).
|
// with the status code from `exit`, à la errx(3).
|
||||||
func Errx(exit int, format string, a ...interface{}) {
|
func Errx(exit int, format string, a ...any) {
|
||||||
format = fmt.Sprintf("[%s] %s", progname, format)
|
format = fmt.Sprintf("[%s] %s", progname, format)
|
||||||
format += "\n"
|
format += "\n"
|
||||||
fmt.Fprintf(os.Stderr, format, a...)
|
fmt.Fprintf(os.Stderr, format, a...)
|
||||||
@@ -47,7 +53,7 @@ func Errx(exit int, format string, a ...interface{}) {
|
|||||||
// Err displays a formatting error message to standard error,
|
// Err displays a formatting error message to standard error,
|
||||||
// appending the error string, and exits with the status code from
|
// appending the error string, and exits with the status code from
|
||||||
// `exit`, à la err(3).
|
// `exit`, à la err(3).
|
||||||
func Err(exit int, err error, format string, a ...interface{}) {
|
func Err(exit int, err error, format string, a ...any) {
|
||||||
format = fmt.Sprintf("[%s] %s", progname, format)
|
format = fmt.Sprintf("[%s] %s", progname, format)
|
||||||
format += ": %v\n"
|
format += ": %v\n"
|
||||||
a = append(a, err)
|
a = append(a, err)
|
||||||
@@ -62,30 +68,30 @@ func Itoa(i int, wid int) string {
|
|||||||
// Assemble decimal in reverse order.
|
// Assemble decimal in reverse order.
|
||||||
var b [20]byte
|
var b [20]byte
|
||||||
bp := len(b) - 1
|
bp := len(b) - 1
|
||||||
for i >= 10 || wid > 1 {
|
for i >= digitWidth || wid > 1 {
|
||||||
wid--
|
wid--
|
||||||
q := i / 10
|
q := i / digitWidth
|
||||||
b[bp] = byte('0' + i - q*10)
|
b[bp] = byte('0' + i - q*digitWidth)
|
||||||
bp--
|
bp--
|
||||||
i = q
|
i = q
|
||||||
}
|
}
|
||||||
// i < 10
|
|
||||||
b[bp] = byte('0' + i)
|
b[bp] = byte('0' + i)
|
||||||
return string(b[bp:])
|
return string(b[bp:])
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dayDuration = 24 * time.Hour
|
dayDuration = 24 * time.Hour
|
||||||
yearDuration = (365 * dayDuration) + (6 * time.Hour)
|
yearDuration = (daysInYear * dayDuration) + (hoursInQuarterDay * time.Hour)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Duration returns a prettier string for time.Durations.
|
// Duration returns a prettier string for time.Durations.
|
||||||
func Duration(d time.Duration) string {
|
func Duration(d time.Duration) string {
|
||||||
var s string
|
var s string
|
||||||
if d >= yearDuration {
|
if d >= yearDuration {
|
||||||
years := d / yearDuration
|
years := int64(d / yearDuration)
|
||||||
s += fmt.Sprintf("%dy", years)
|
s += fmt.Sprintf("%dy", years)
|
||||||
d -= years * yearDuration
|
d -= time.Duration(years) * yearDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
if d >= dayDuration {
|
if d >= dayDuration {
|
||||||
@@ -98,8 +104,8 @@ func Duration(d time.Duration) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
d %= 1 * time.Second
|
d %= 1 * time.Second
|
||||||
hours := d / time.Hour
|
hours := int64(d / time.Hour)
|
||||||
d -= hours * time.Hour
|
d -= time.Duration(hours) * time.Hour
|
||||||
s += fmt.Sprintf("%dh%s", hours, d)
|
s += fmt.Sprintf("%dh%s", hours, d)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package logging
|
package logging
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"errors"
|
||||||
"os"
|
"fmt"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
// File writes its logs to file.
|
// File writes its logs to file.
|
||||||
@@ -59,12 +60,12 @@ func NewSplitFile(outpath, errpath string, overwrite bool) (*File, error) {
|
|||||||
fl.fe, err = os.OpenFile(errpath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
|
fl.fe, err = os.OpenFile(errpath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if closeErr := fl.Close(); closeErr != nil {
|
if closeErr := fl.Close(); closeErr != nil {
|
||||||
return nil, fmt.Errorf("failed to open error log: cleanup close failed: %v: %w", closeErr, err)
|
return nil, fmt.Errorf("failed to open error log: %w", errors.Join(closeErr, err))
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to open error log: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fl.LogWriter = NewLogWriter(fl.fo, fl.fe)
|
fl.LogWriter = NewLogWriter(fl.fo, fl.fe)
|
||||||
return fl, nil
|
return fl, nil
|
||||||
@@ -94,13 +95,13 @@ func (fl *File) Flush() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fl *File) Chmod(mode os.FileMode) error {
|
func (fl *File) Chmod(mode os.FileMode) error {
|
||||||
if err := fl.fo.Chmod(mode); err != nil {
|
if err := fl.fo.Chmod(mode); err != nil {
|
||||||
return fmt.Errorf("failed to chmod output log: %w", err)
|
return fmt.Errorf("failed to chmod output log: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := fl.fe.Chmod(mode); err != nil {
|
if err := fl.fe.Chmod(mode); err != nil {
|
||||||
return fmt.Errorf("failed to chmod error log: %w", err)
|
return fmt.Errorf("failed to chmod error log: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user