Compare commits

...

16 Commits

Author SHA1 Message Date
80b3376fa5 Update CHANGELOG for v1.15.7. 2025-11-19 14:50:22 -08:00
603724c2c9 cmd/tlsinfo: fix typo in output. 2025-11-19 14:48:02 -08:00
85de524a02 certlib/certgen: GenerateKey was generating wrong key type.
The ed25519 block was being used to generate RSA keys.
2025-11-19 14:46:54 -08:00
02fb85aec0 certlib: update FileKind with algo information.
Additionally, key algo wasn't being set on PEM files.
2025-11-19 14:46:17 -08:00
b1a2039c7d Update CHANGELOG for v1.15.6. 2025-11-19 09:46:09 -08:00
46c9976e73 certlib: Add file kind functionality. 2025-11-19 09:45:57 -08:00
5a5dd5e6ea Update CHANGELOG for v1.15.5. 2025-11-19 08:45:08 -08:00
3317b8c33b certlib/bundler: add support for pemcrt. 2025-11-19 08:43:46 -08:00
fb1b1ffcad Update CHANGELOG for v1.15.4. 2025-11-19 02:57:40 -08:00
7bb6973341 QoL for CSR generation. 2025-11-19 02:57:26 -08:00
8e997bda34 Update CHANGELOG for v1.15.3. 2025-11-19 02:43:47 -08:00
d76db4a947 Minor bug fixes. 2025-11-19 02:43:25 -08:00
7e36a828d4 Update CHANGELOG for v1.15.2. 2025-11-19 02:20:36 -08:00
8eaca580be Minor bug fixes. 2025-11-19 02:20:21 -08:00
fd31e31afa Update CHANGELOG for v1.15.1. 2025-11-19 01:47:48 -08:00
7426988ae4 linter fixes. 2025-11-19 01:47:42 -08:00
28 changed files with 484 additions and 75 deletions

View File

@@ -101,7 +101,7 @@ linters:
- loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap) - loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap)
- makezero # finds slice declarations with non-zero initial length - makezero # finds slice declarations with non-zero initial length
- mirror # reports wrong mirror patterns of bytes/strings usage - mirror # reports wrong mirror patterns of bytes/strings usage
- mnd # detects magic numbers # - mnd # detects magic numbers
- modernize # suggests simplifications to Go code, using modern language and library features - modernize # suggests simplifications to Go code, using modern language and library features
- musttag # enforces field tags in (un)marshaled structs - musttag # enforces field tags in (un)marshaled structs
- nakedret # finds naked returns in functions greater than a specified function length - nakedret # finds naked returns in functions greater than a specified function length

View File

@@ -1,5 +1,37 @@
CHANGELOG CHANGELOG
v1.15.7 - 2025-11-19
Changed:
- certlib: update FileKind with algo information and fix bug where PEM
files didn't have their algorithm set.
- certlib/certgen: GenerateKey had the blocks for Ed25519 and RSA keys
swapped.
- cmd/tlsinfo: fix type in output.
v1.15.6 - 2025-11-19
certlib: add FileKind function to determine file type.
v1.15.5 - 2025-11-19
certlib/bundler: add support for crt files that are pem-encoded.
v1.15.4 - 2025-11-19
Quality of life fixes for CSR generation.
v1.15.3 - 2025-11-19
Minor bug fixes.
v1.15.2 - 2025-11-19
Minor bug fixes.
v1.15.1 - 2025-11-19
Changed:
- linter fixes.
Removed:
- mnd removed from linter.
v1.15.0 - 2025-11-19 v1.15.0 - 2025-11-19
Changed: Changed:

View File

@@ -422,6 +422,24 @@ func encodeCertsToFiles(
name: baseName + ".pem", name: baseName + ".pem",
content: pemContent, content: pemContent,
}) })
case "crt":
pemContent := encodeCertsToPEM(certs)
files = append(files, fileEntry{
name: baseName + ".crt",
content: pemContent,
})
case "pemcrt":
pemContent := encodeCertsToPEM(certs)
files = append(files, fileEntry{
name: baseName + ".pem",
content: pemContent,
})
pemContent = encodeCertsToPEM(certs)
files = append(files, fileEntry{
name: baseName + ".crt",
content: pemContent,
})
case "der": case "der":
if isSingle { if isSingle {
// For single file in DER, concatenate all cert DER bytes // For single file in DER, concatenate all cert DER bytes

View File

@@ -63,12 +63,7 @@ type CertificateRequest struct {
Profile Profile `yaml:"profile"` Profile Profile `yaml:"profile"`
} }
func (cs CertificateRequest) Generate() (crypto.PrivateKey, *x509.CertificateRequest, error) { func (cs CertificateRequest) Request(priv crypto.PrivateKey) (*x509.CertificateRequest, error) {
pub, priv, err := cs.KeySpec.Generate()
if err != nil {
return nil, nil, err
}
subject := pkix.Name{} subject := pkix.Name{}
subject.CommonName = cs.Subject.CommonName subject.CommonName = cs.Subject.CommonName
subject.Country = []string{cs.Subject.Country} subject.Country = []string{cs.Subject.Country}
@@ -81,13 +76,13 @@ func (cs CertificateRequest) Generate() (crypto.PrivateKey, *x509.CertificateReq
for i, ip := range cs.Subject.IPAddresses { for i, ip := range cs.Subject.IPAddresses {
ipAddresses = append(ipAddresses, net.ParseIP(ip)) ipAddresses = append(ipAddresses, net.ParseIP(ip))
if ipAddresses[i] == nil { if ipAddresses[i] == nil {
return nil, nil, fmt.Errorf("invalid IP address: %s", ip) return nil, fmt.Errorf("invalid IP address: %s", ip)
} }
} }
req := &x509.CertificateRequest{ req := &x509.CertificateRequest{
PublicKeyAlgorithm: 0, PublicKeyAlgorithm: 0,
PublicKey: pub, PublicKey: getPublic(priv),
Subject: subject, Subject: subject,
DNSNames: cs.Subject.DNSNames, DNSNames: cs.Subject.DNSNames,
IPAddresses: ipAddresses, IPAddresses: ipAddresses,
@@ -95,12 +90,26 @@ func (cs CertificateRequest) Generate() (crypto.PrivateKey, *x509.CertificateReq
reqBytes, err := x509.CreateCertificateRequest(rand.Reader, req, priv) reqBytes, err := x509.CreateCertificateRequest(rand.Reader, req, priv)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to create certificate request: %w", err) return nil, fmt.Errorf("failed to create certificate request: %w", err)
} }
req, err = x509.ParseCertificateRequest(reqBytes) req, err = x509.ParseCertificateRequest(reqBytes)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to parse certificate request: %w", err) return nil, fmt.Errorf("failed to parse certificate request: %w", err)
}
return req, nil
}
func (cs CertificateRequest) Generate() (crypto.PrivateKey, *x509.CertificateRequest, error) {
_, priv, err := cs.KeySpec.Generate()
if err != nil {
return nil, nil, err
}
req, err := cs.Request(priv)
if err != nil {
return nil, nil, err
} }
return priv, req, nil return priv, req, nil
@@ -158,7 +167,11 @@ func (p Profile) templateFromRequest(req *x509.CertificateRequest) (*x509.Certif
return certTemplate, nil return certTemplate, nil
} }
func (p Profile) SignRequest(parent *x509.Certificate, req *x509.CertificateRequest, priv crypto.PrivateKey) (*x509.Certificate, error) { func (p Profile) SignRequest(
parent *x509.Certificate,
req *x509.CertificateRequest,
priv crypto.PrivateKey,
) (*x509.Certificate, error) {
tpl, err := p.templateFromRequest(req) tpl, err := p.templateFromRequest(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create certificate template: %w", err) return nil, fmt.Errorf("failed to create certificate template: %w", err)

View File

@@ -8,14 +8,13 @@ import (
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
"encoding/asn1"
"errors" "errors"
"fmt" "fmt"
) )
var ( // var (
oidEd25519 = asn1.ObjectIdentifier{1, 3, 101, 110} // oidEd25519 = asn1.ObjectIdentifier{1, 3, 101, 110}
) //)
func GenerateKey(algorithm x509.PublicKeyAlgorithm, bitSize int) (crypto.PublicKey, crypto.PrivateKey, error) { func GenerateKey(algorithm x509.PublicKeyAlgorithm, bitSize int) (crypto.PublicKey, crypto.PrivateKey, error) {
var key crypto.PrivateKey var key crypto.PrivateKey
@@ -23,12 +22,17 @@ func GenerateKey(algorithm x509.PublicKeyAlgorithm, bitSize int) (crypto.PublicK
var err error var err error
switch algorithm { switch algorithm {
case x509.RSA:
pub, key, err = ed25519.GenerateKey(rand.Reader)
case x509.Ed25519: case x509.Ed25519:
pub, key, err = ed25519.GenerateKey(rand.Reader)
case x509.RSA:
key, err = rsa.GenerateKey(rand.Reader, bitSize) key, err = rsa.GenerateKey(rand.Reader, bitSize)
if err == nil { if err == nil {
pub = key.(*rsa.PrivateKey).Public() rsaPriv, ok := key.(*rsa.PrivateKey)
if !ok {
panic("failed to cast RSA private key to *rsa.PrivateKey")
}
pub = rsaPriv.Public()
} }
case x509.ECDSA: case x509.ECDSA:
var curve elliptic.Curve var curve elliptic.Curve
@@ -46,8 +50,17 @@ func GenerateKey(algorithm x509.PublicKeyAlgorithm, bitSize int) (crypto.PublicK
key, err = ecdsa.GenerateKey(curve, rand.Reader) key, err = ecdsa.GenerateKey(curve, rand.Reader)
if err == nil { if err == nil {
pub = key.(*ecdsa.PrivateKey).Public() ecPriv, ok := key.(*ecdsa.PrivateKey)
if !ok {
panic("failed to cast ECDSA private key to *ecdsa.PrivateKey")
}
pub = ecPriv.Public()
} }
case x509.DSA:
fallthrough
case x509.UnknownPublicKeyAlgorithm:
fallthrough
default: default:
err = errors.New("unsupported algorithm") err = errors.New("unsupported algorithm")
} }
@@ -58,3 +71,16 @@ func GenerateKey(algorithm x509.PublicKeyAlgorithm, bitSize int) (crypto.PublicK
return pub, key, nil return pub, key, nil
} }
func getPublic(priv crypto.PrivateKey) crypto.PublicKey {
switch priv := priv.(type) {
case *rsa.PrivateKey:
return &priv.PublicKey
case *ecdsa.PrivateKey:
return &priv.PublicKey
case *ed25519.PrivateKey:
return priv.Public()
default:
return nil
}
}

View File

@@ -1,11 +1,19 @@
package certlib package certlib
import ( import (
"bytes"
"crypto"
"crypto/dsa"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rsa"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
"os" "os"
"strings"
"git.wntrmute.dev/kyle/goutils/certlib/certerr" "git.wntrmute.dev/kyle/goutils/certlib/certerr"
) )
@@ -13,6 +21,7 @@ import (
// ReadCertificate reads a DER or PEM-encoded certificate from the // ReadCertificate reads a DER or PEM-encoded certificate from the
// byte slice. // byte slice.
func ReadCertificate(in []byte) (*x509.Certificate, []byte, error) { func ReadCertificate(in []byte) (*x509.Certificate, []byte, error) {
in = bytes.TrimSpace(in)
if len(in) == 0 { if len(in) == 0 {
return nil, nil, certerr.ParsingError(certerr.ErrorSourceCertificate, certerr.ErrEmptyCertificate) return nil, nil, certerr.ParsingError(certerr.ErrorSourceCertificate, certerr.ErrEmptyCertificate)
} }
@@ -24,10 +33,10 @@ func ReadCertificate(in []byte) (*x509.Certificate, []byte, error) {
} }
rest := remaining rest := remaining
if p.Type != "CERTIFICATE" { if p.Type != pemTypeCertificate {
return nil, rest, certerr.ParsingError( return nil, rest, certerr.ParsingError(
certerr.ErrorSourceCertificate, certerr.ErrorSourceCertificate,
certerr.ErrInvalidPEMType(p.Type, "CERTIFICATE"), certerr.ErrInvalidPEMType(p.Type, pemTypeCertificate),
) )
} }
@@ -109,3 +118,179 @@ func PoolFromBytes(certBytes []byte) (*x509.CertPool, error) {
return pool, nil return pool, nil
} }
func ExportPrivateKeyPEM(priv crypto.PrivateKey) ([]byte, error) {
keyDER, err := x509.MarshalPKCS8PrivateKey(priv)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{Type: pemTypePrivateKey, Bytes: keyDER}), nil
}
func LoadCSR(path string) (*x509.CertificateRequest, error) {
in, err := os.ReadFile(path)
if err != nil {
return nil, certerr.LoadingError(certerr.ErrorSourceCSR, err)
}
req, _, err := ParseCSR(in)
return req, err
}
func ExportCSRAsPEM(req *x509.CertificateRequest) []byte {
return pem.EncodeToMemory(&pem.Block{Type: pemTypeCertificateRequest, Bytes: req.Raw})
}
type FileFormat uint8
const (
FormatPEM FileFormat = iota + 1
FormatDER
)
func (f FileFormat) String() string {
switch f {
case FormatPEM:
return "PEM"
case FormatDER:
return "DER"
default:
return "unknown"
}
}
type KeyAlgo struct {
Type x509.PublicKeyAlgorithm
Size int
curve elliptic.Curve
}
func (ka KeyAlgo) String() string {
switch ka.Type {
case x509.RSA:
return fmt.Sprintf("RSA-%d", ka.Size)
case x509.ECDSA:
return fmt.Sprintf("ECDSA-%s", ka.curve.Params().Name)
case x509.Ed25519:
return "Ed25519"
case x509.DSA:
return "DSA"
default:
return "unknown"
}
}
func publicKeyAlgoFromPublicKey(key crypto.PublicKey) KeyAlgo {
switch key := key.(type) {
case *rsa.PublicKey:
return KeyAlgo{
Type: x509.RSA,
Size: key.N.BitLen(),
}
case *ecdsa.PublicKey:
return KeyAlgo{
Type: x509.ECDSA,
curve: key.Curve,
Size: key.Params().BitSize,
}
case *ed25519.PublicKey:
return KeyAlgo{
Type: x509.Ed25519,
}
case *dsa.PublicKey:
return KeyAlgo{
Type: x509.DSA,
}
default:
return KeyAlgo{
Type: x509.UnknownPublicKeyAlgorithm,
}
}
}
func publicKeyAlgoFromKey(key crypto.PrivateKey) KeyAlgo {
switch key := key.(type) {
case *rsa.PrivateKey:
return KeyAlgo{
Type: x509.RSA,
Size: key.PublicKey.N.BitLen(),
}
case *ecdsa.PrivateKey:
return KeyAlgo{
Type: x509.ECDSA,
curve: key.PublicKey.Curve,
Size: key.Params().BitSize,
}
case *ed25519.PrivateKey:
return KeyAlgo{
Type: x509.Ed25519,
}
case *dsa.PrivateKey:
return KeyAlgo{
Type: x509.DSA,
}
default:
return KeyAlgo{
Type: x509.UnknownPublicKeyAlgorithm,
}
}
}
func publicKeyAlgoFromCert(cert *x509.Certificate) KeyAlgo {
return publicKeyAlgoFromPublicKey(cert.PublicKey)
}
func publicKeyAlgoFromCSR(csr *x509.CertificateRequest) KeyAlgo {
return publicKeyAlgoFromPublicKey(csr.PublicKeyAlgorithm)
}
type FileType struct {
Format FileFormat
Type string
Algo KeyAlgo
}
func (ft FileType) String() string {
if ft.Type == "" {
return ft.Format.String()
}
return fmt.Sprintf("%s %s (%s)", ft.Algo, ft.Type, ft.Format)
}
// FileKind returns the file type of the given file.
func FileKind(path string) (*FileType, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
ft := &FileType{Format: FormatDER}
block, _ := pem.Decode(data)
if block != nil {
data = block.Bytes
ft.Type = strings.ToLower(block.Type)
ft.Format = FormatPEM
}
cert, err := x509.ParseCertificate(data)
if err == nil {
ft.Algo = publicKeyAlgoFromCert(cert)
return ft, nil
}
csr, err := x509.ParseCertificateRequest(data)
if err == nil {
ft.Algo = publicKeyAlgoFromCSR(csr)
return ft, nil
}
priv, err := x509.ParsePKCS8PrivateKey(data)
if err == nil {
ft.Algo = publicKeyAlgoFromKey(priv)
return ft, nil
}
return nil, errors.New("certlib; unknown file type")
}

View File

@@ -2,7 +2,10 @@
package certlib package certlib
import ( import (
"crypto/elliptic"
"crypto/x509"
"fmt" "fmt"
"strings"
"testing" "testing"
"git.wntrmute.dev/kyle/goutils/assert" "git.wntrmute.dev/kyle/goutils/assert"
@@ -138,3 +141,33 @@ func TestReadCertificates(t *testing.T) {
assert.BoolT(t, cert != nil, "lib: expected an actual certificate to have been returned") assert.BoolT(t, cert != nil, "lib: expected an actual certificate to have been returned")
} }
} }
var (
ecTestCACert = "testdata/ec-ca-cert.pem"
ecTestCAPriv = "testdata/ec-ca-priv.pem"
ecTestCAReq = "testdata/ec-ca-cert.csr"
)
func TestFileTypeEC(t *testing.T) {
ft, err := FileKind(ecTestCAPriv)
assert.NoErrorT(t, err)
if ft.Format != FormatPEM {
t.Errorf("certlib: expected format '%s', got '%s'", FormatPEM, ft.Format)
}
if ft.Type != strings.ToLower(pemTypePrivateKey) {
t.Errorf("certlib: expected type '%s', got '%s'",
strings.ToLower(pemTypePrivateKey), ft.Type)
}
expectedAlgo := KeyAlgo{
Type: x509.ECDSA,
Size: 521,
curve: elliptic.P521(),
}
if ft.Algo.String() != expectedAlgo.String() {
t.Errorf("certlib: expected algo '%s', got '%s'", expectedAlgo, ft.Algo)
}
}

View File

@@ -54,8 +54,6 @@ var extKeyUsages = map[x509.ExtKeyUsage]string{
x509.ExtKeyUsageMicrosoftKernelCodeSigning: "microsoft kernel code signing", x509.ExtKeyUsageMicrosoftKernelCodeSigning: "microsoft kernel code signing",
} }
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:
@@ -251,11 +249,6 @@ func showBasicConstraints(cert *x509.Certificate) {
fmt.Fprintln(os.Stdout) fmt.Fprintln(os.Stdout)
} }
var (
dateFormat string
showHash bool // if true, print a SHA256 hash of the certificate's Raw field
)
func wrapPrint(text string, indent int) { func wrapPrint(text string, indent int) {
tabs := "" tabs := ""
var tabsSb140 strings.Builder var tabsSb140 strings.Builder
@@ -267,11 +260,12 @@ func wrapPrint(text string, indent int) {
fmt.Fprintf(os.Stdout, tabs+"%s\n", wrap(text, indent)) fmt.Fprintf(os.Stdout, tabs+"%s\n", wrap(text, indent))
} }
func DisplayCert(w io.Writer, cert *x509.Certificate) { func DisplayCert(w io.Writer, cert *x509.Certificate, showHash bool) {
fmt.Fprintln(w, "CERTIFICATE") fmt.Fprintln(w, "CERTIFICATE")
if showHash { if showHash {
fmt.Fprintln(w, wrap(fmt.Sprintf("SHA256: %x", sha256.Sum256(cert.Raw)), 0)) fmt.Fprintln(w, wrap(fmt.Sprintf("SHA256: %x", sha256.Sum256(cert.Raw)), 0))
} }
fmt.Fprintln(w, wrap("Subject: "+DisplayName(cert.Subject), 0)) fmt.Fprintln(w, wrap("Subject: "+DisplayName(cert.Subject), 0))
fmt.Fprintln(w, wrap("Issuer: "+DisplayName(cert.Issuer), 0)) fmt.Fprintln(w, wrap("Issuer: "+DisplayName(cert.Issuer), 0))
fmt.Fprintf(w, "\tSignature algorithm: %s / %s\n", sigAlgoPK(cert.SignatureAlgorithm), fmt.Fprintf(w, "\tSignature algorithm: %s / %s\n", sigAlgoPK(cert.SignatureAlgorithm),
@@ -287,8 +281,8 @@ func DisplayCert(w io.Writer, cert *x509.Certificate) {
fmt.Fprintf(w, "\t%s\n", wrap("SKI: "+dumpHex(cert.SubjectKeyId), 1)) fmt.Fprintf(w, "\t%s\n", wrap("SKI: "+dumpHex(cert.SubjectKeyId), 1))
} }
wrapPrint("Valid from: "+cert.NotBefore.Format(dateFormat), 1) wrapPrint("Valid from: "+cert.NotBefore.Format(lib.DateShortFormat), 1)
fmt.Fprintf(w, "\t until: %s\n", cert.NotAfter.Format(dateFormat)) fmt.Fprintf(w, "\t until: %s\n", cert.NotAfter.Format(lib.DateShortFormat))
fmt.Fprintf(w, "\tKey usages: %s\n", keyUsages(cert.KeyUsage)) fmt.Fprintf(w, "\tKey usages: %s\n", keyUsages(cert.KeyUsage))
if len(cert.ExtKeyUsage) > 0 { if len(cert.ExtKeyUsage) > 0 {

View File

@@ -75,6 +75,12 @@ var DelegationExtension = pkix.Extension{
Value: []byte{0x05, 0x00}, // ASN.1 NULL Value: []byte{0x05, 0x00}, // ASN.1 NULL
} }
const (
pemTypeCertificate = "CERTIFICATE"
pemTypeCertificateRequest = "CERTIFICATE REQUEST"
pemTypePrivateKey = "PRIVATE KEY"
)
// InclusiveDate returns the time.Time representation of a date - 1 // InclusiveDate returns the time.Time representation of a date - 1
// nanosecond. This allows time.After to be used inclusively. // nanosecond. This allows time.After to be used inclusively.
func InclusiveDate(year int, month time.Month, day int) time.Time { func InclusiveDate(year int, month time.Month, day int) time.Time {
@@ -246,7 +252,7 @@ func EncodeCertificatesPEM(certs []*x509.Certificate) []byte {
var buffer bytes.Buffer var buffer bytes.Buffer
for _, cert := range certs { for _, cert := range certs {
if err := pem.Encode(&buffer, &pem.Block{ if err := pem.Encode(&buffer, &pem.Block{
Type: "CERTIFICATE", Type: pemTypeCertificate,
Bytes: cert.Raw, Bytes: cert.Raw,
}); err != nil { }); err != nil {
return nil return nil

View File

@@ -1,6 +1,10 @@
package certlib package certlib_test
import "testing" import (
"testing"
"git.wntrmute.dev/kyle/goutils/certlib"
)
var ( var (
testCert1 = "testdata/cert1.pem" testCert1 = "testdata/cert1.pem"
@@ -16,25 +20,25 @@ type testCase struct {
} }
var testCases = []testCase{ var testCases = []testCase{
{"testdata/cert1.pem", "testdata/priv1.pem", true}, {testCert1, testPriv1, true},
{"testdata/cert2.pem", "testdata/priv2.pem", true}, {testCert2, testPriv2, true},
{"testdata/cert1.pem", "testdata/priv2.pem", false}, {testCert1, testPriv2, false},
{"testdata/cert2.pem", "testdata/priv1.pem", false}, {testCert2, testPriv1, false},
} }
func TestMatchKeys(t *testing.T) { func TestMatchKeys(t *testing.T) {
for i, tc := range testCases { for i, tc := range testCases {
cert, err := LoadCertificate(tc.cert) cert, err := certlib.LoadCertificate(tc.cert)
if err != nil { if err != nil {
t.Fatalf("failed to load cert %d: %v", i, err) t.Fatalf("failed to load cert %d: %v", i, err)
} }
priv, err := LoadPrivateKey(tc.key) priv, err := certlib.LoadPrivateKey(tc.key)
if err != nil { if err != nil {
t.Fatalf("failed to load key %d: %v", i, err) t.Fatalf("failed to load key %d: %v", i, err)
} }
ok, _ := MatchKeys(cert, priv) ok, _ := certlib.MatchKeys(cert, priv)
switch { switch {
case ok && !tc.match: case ok && !tc.match:
t.Fatalf("case %d: cert %s/key %s should not match", i, tc.cert, tc.key) t.Fatalf("case %d: cert %s/key %s should not match", i, tc.cert, tc.key)

12
certlib/testdata/ec-ca-cert.csr vendored Normal file
View File

@@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBzTCCAS4CAQAwgYgxCzAJBgNVBAYTAlVTMQkwBwYDVQQIEwAxCTAHBgNVBAcT
ADEiMCAGA1UEChMZV05UUk1VVEUgSEVBVlkgSU5EVVNUUklFUzEfMB0GA1UECxMW
Q1JZUFRPR1JBUEhJQyBTRVJWSUNFUzEeMBwGA1UEAxMVV05UUk1VVEUgVEVTVCBF
QyBDQSAxMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAQxmTxzo1XOK0HDrtn92b
exC4sXr8GnU+oATiXied3e1AWVOux9XtaWduY+a+r6Kb1rxMVyebn9KqtwNw+9KS
XaEB1IN9QzfdxEcJgRIAVtFplOqCip5xKK0B+woo3wXm3ndq2kJts86aONqQ0m2g
RrsmAKAX4pwmMnAHFF7veBcpsqugADAKBggqhkjOPQQDBAOBjAAwgYgCQgDG8Hdu
FkC3z0u0MU01+Bi/2MorcVTvdkurLm6Rh2Zf65aaXK8PDdV/cPZ98qx7NoLDSvwF
83gJuUI/3nVB/Ith7wJCAb6SAkXroT7y41XHayyTYb6+RKSlxxb9e5rtVCp/nG23
s59r23vUC/wDb4VWJE5jKi5vmXfjY+RAL9FOnpr2wsX0
-----END CERTIFICATE REQUEST-----

18
certlib/testdata/ec-ca-cert.pem vendored Normal file
View File

@@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC4TCCAkKgAwIBAgIUSnrCuvU8kj0nxNzmTgibiPLrQ8QwCgYIKoZIzj0EAwQw
gYgxCzAJBgNVBAYTAlVTMQkwBwYDVQQIEwAxCTAHBgNVBAcTADEiMCAGA1UEChMZ
V05UUk1VVEUgSEVBVlkgSU5EVVNUUklFUzEfMB0GA1UECxMWQ1JZUFRPR1JBUEhJ
QyBTRVJWSUNFUzEeMBwGA1UEAxMVV05UUk1VVEUgVEVTVCBFQyBDQSAxMB4XDTI1
MTExOTIwNTgwMVoXDTQ1MTExNDIxNTgwMVowgYgxCzAJBgNVBAYTAlVTMQkwBwYD
VQQIEwAxCTAHBgNVBAcTADEiMCAGA1UEChMZV05UUk1VVEUgSEVBVlkgSU5EVVNU
UklFUzEfMB0GA1UECxMWQ1JZUFRPR1JBUEhJQyBTRVJWSUNFUzEeMBwGA1UEAxMV
V05UUk1VVEUgVEVTVCBFQyBDQSAxMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQA
QxmTxzo1XOK0HDrtn92bexC4sXr8GnU+oATiXied3e1AWVOux9XtaWduY+a+r6Kb
1rxMVyebn9KqtwNw+9KSXaEB1IN9QzfdxEcJgRIAVtFplOqCip5xKK0B+woo3wXm
3ndq2kJts86aONqQ0m2gRrsmAKAX4pwmMnAHFF7veBcpsqujRTBDMA4GA1UdDwEB
/wQEAwICBDASBgNVHRMBAf8ECDAGAQH/AgEDMB0GA1UdDgQWBBSNqRkvwUgIHGa2
jKmA2Q3w6Ju/FzAKBggqhkjOPQQDBAOBjAAwgYgCQgCckIFCjzJExxbV9dqm92nr
safC3kqhCxjmilf0IYWVj5f1kymoFr3jPpmy0iFcteUk0QTcqpnUT4i140lxtyK8
NAJCAVxbicZgVns9rgp6hu14l81j0XMpNgzy0QxscjMpWS/17iDJ4Y5vCWpwekrr
F1cmmRpsodONacAvTml4ehKE2ekx
-----END CERTIFICATE-----

8
certlib/testdata/ec-ca-priv.pem vendored Normal file
View File

@@ -0,0 +1,8 @@
-----BEGIN PRIVATE KEY-----
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAzkf/rvLGJBTVHHHr
lUhzsRJZgkyzSY5YE3KBReDyFWc+OB48C1gdYB1u7+PxgyfwYACjPx2y1AxN8fJh
XonY39mhgYkDgYYABABDGZPHOjVc4rQcOu2f3Zt7ELixevwadT6gBOJeJ53d7UBZ
U67H1e1pZ25j5r6vopvWvExXJ5uf0qq3A3D70pJdoQHUg31DN93ERwmBEgBW0WmU
6oKKnnEorQH7CijfBebed2raQm2zzpo42pDSbaBGuyYAoBfinCYycAcUXu94Fymy
qw==
-----END PRIVATE KEY-----

13
certlib/testdata/ec-ca.yaml vendored Normal file
View File

@@ -0,0 +1,13 @@
key:
algorithm: ecdsa
size: 521
subject:
common_name: WNTRMUTE TEST EC CA 1
country: US
organization: WNTRMUTE HEAVY INDUSTRIES
organizational_unit: CRYPTOGRAPHIC SERVICES
profile:
is_ca: true
path_len: 3
key_uses: cert sign
expiry: 20y

8
certlib/testdata/rsa-ca-cert.csr vendored Normal file
View File

@@ -0,0 +1,8 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBCjCBvQIBADCBiTELMAkGA1UEBhMCVVMxCTAHBgNVBAgTADEJMAcGA1UEBxMA
MSIwIAYDVQQKExlXTlRSTVVURSBIRUFWWSBJTkRVU1RSSUVTMR8wHQYDVQQLExZD
UllQVE9HUkFQSElDIFNFUlZJQ0VTMR8wHQYDVQQDExZXTlRSTVVURSBURVNUIFJT
QSBDQSAxMCowBQYDK2VwAyEA1Lai2WChuUH2kq4LWddp6TlcmpuuBz6G43e9efsZ
GBqgADAFBgMrZXADQQDbBl1gW07c0g9UQmK2g8QkVIXzr2TLrOjXVAptlcW/3rPO
M3iQM2mGwZWMwv7t6C4C7xBaLcUkcqT3b4S+MaUK
-----END CERTIFICATE REQUEST-----

14
certlib/testdata/rsa-ca-cert.pem vendored Normal file
View File

@@ -0,0 +1,14 @@
-----BEGIN CERTIFICATE-----
MIICHDCCAc6gAwIBAgIVAN1AKHhLNsqcBEKYCqgjEMG65hhvMAUGAytlcDCBiTEL
MAkGA1UEBhMCVVMxCTAHBgNVBAgTADEJMAcGA1UEBxMAMSIwIAYDVQQKExlXTlRS
TVVURSBIRUFWWSBJTkRVU1RSSUVTMR8wHQYDVQQLExZDUllQVE9HUkFQSElDIFNF
UlZJQ0VTMR8wHQYDVQQDExZXTlRSTVVURSBURVNUIFJTQSBDQSAxMB4XDTI1MTEx
OTIxMDQyNVoXDTQ1MTExNDIyMDQyNVowgYkxCzAJBgNVBAYTAlVTMQkwBwYDVQQI
EwAxCTAHBgNVBAcTADEiMCAGA1UEChMZV05UUk1VVEUgSEVBVlkgSU5EVVNUUklF
UzEfMB0GA1UECxMWQ1JZUFRPR1JBUEhJQyBTRVJWSUNFUzEfMB0GA1UEAxMWV05U
Uk1VVEUgVEVTVCBSU0EgQ0EgMTAqMAUGAytlcAMhANS2otlgoblB9pKuC1nXaek5
XJqbrgc+huN3vXn7GRgao0UwQzAOBgNVHQ8BAf8EBAMCAgQwEgYDVR0TAQH/BAgw
BgEB/wIBAzAdBgNVHQ4EFgQUetUgY5rlFq+OCeYe0Eqmp8Ek488wBQYDK2VwA0EA
LIFZo6FQL+8q8h66Bm7favIh2AlqsXA45DpRUN2LpjNm/7NbTPDw52y8cLegUUMc
UhDyk20fGg5g6cLywC0mDA==
-----END CERTIFICATE-----

3
certlib/testdata/rsa-ca-priv.pem vendored Normal file
View File

@@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIDDkYbIZKArACSevxtX2Rr8MQSeJ4Jz0qJEe/YgHfjzo
-----END PRIVATE KEY-----

13
certlib/testdata/rsa-ca.yaml vendored Normal file
View File

@@ -0,0 +1,13 @@
key:
algorithm: ed25519
size: 4096
subject:
common_name: WNTRMUTE TEST RSA CA 1
country: US
organization: WNTRMUTE HEAVY INDUSTRIES
organizational_unit: CRYPTOGRAPHIC SERVICES
profile:
is_ca: true
path_len: 3
key_uses: cert sign
expiry: 20y

View File

@@ -12,6 +12,7 @@ chains:
include_single: true include_single: true
include_individual: true include_individual: true
manifest: true manifest: true
encoding: pemcrt
formats: formats:
- zip - zip
- tgz - tgz
@@ -53,4 +54,4 @@ chains:
manifest: false manifest: false
encoding: both encoding: both
formats: formats:
- zip - zip

View File

@@ -39,7 +39,7 @@ func main() {
revoke.HardFail = hardfail revoke.HardFail = hardfail
// Build a proxy-aware HTTP client for OCSP/CRL fetches // Build a proxy-aware HTTP client for OCSP/CRL fetches
if httpClient, err := dialer.NewHTTPClient(dialer.DialerOpts{Timeout: timeout}); err == nil { if httpClient, err := dialer.NewHTTPClient(dialer.Opts{Timeout: timeout}); err == nil {
revoke.HTTPClient = httpClient revoke.HTTPClient = httpClient
} }
@@ -105,7 +105,7 @@ func checkSite(hostport string) (string, error) {
defer cancel() defer cancel()
// Use proxy-aware TLS dialer // Use proxy-aware TLS dialer
conn, err := dialer.DialTLS(ctx, target.String(), dialer.DialerOpts{Timeout: timeout, TLSConfig: &tls.Config{ conn, err := dialer.DialTLS(ctx, target.String(), dialer.Opts{Timeout: timeout, TLSConfig: &tls.Config{
InsecureSkipVerify: true, // #nosec G402 -- CLI tool only verifies revocation InsecureSkipVerify: true, // #nosec G402 -- CLI tool only verifies revocation
ServerName: target.Host, ServerName: target.Host,
}}) }})

View File

@@ -25,7 +25,11 @@ func main() {
} }
// Use proxy-aware TLS dialer // Use proxy-aware TLS dialer
conn, err := dialer.DialTLS(context.Background(), server, dialer.DialerOpts{TLSConfig: &tls.Config{}}) // #nosec G402 conn, err := dialer.DialTLS(
context.Background(),
server,
dialer.Opts{TLSConfig: &tls.Config{}},
) // #nosec G402
die.If(err) die.If(err)
defer conn.Close() defer conn.Close()

View File

@@ -35,12 +35,12 @@ func main() {
} }
if config.leafOnly { if config.leafOnly {
dump.DisplayCert(os.Stdout, certs[0]) dump.DisplayCert(os.Stdout, certs[0], config.showHash)
continue continue
} }
for i := range certs { for i := range certs {
dump.DisplayCert(os.Stdout, certs[i]) dump.DisplayCert(os.Stdout, certs[i], config.showHash)
} }
} }
} }

View File

@@ -85,7 +85,7 @@ func main() {
continue continue
} }
// Use proxy-aware HTTP client with a reasonable timeout for connects/handshakes // Use proxy-aware HTTP client with a reasonable timeout for connects/handshakes
httpClient, err := dialer.NewHTTPClient(dialer.DialerOpts{Timeout: 30 * time.Second}) httpClient, err := dialer.NewHTTPClient(dialer.Opts{Timeout: 30 * time.Second})
if err != nil { if err != nil {
_, _ = lib.Warn(err, "building HTTP client for %s", remote) _, _ = lib.Warn(err, "building HTTP client for %s", remote)
continue continue

View File

@@ -43,7 +43,7 @@ func main() {
} }
var conn *tls.Conn var conn *tls.Conn
conn, err = dialer.DialTLS(context.Background(), site, dialer.DialerOpts{TLSConfig: tlsCfg}) conn, err = dialer.DialTLS(context.Background(), site, dialer.Opts{TLSConfig: tlsCfg})
die.If(err) die.If(err)
cs := conn.ConnectionState() cs := conn.ConnectionState()

View File

@@ -25,7 +25,7 @@ func main() {
conn, err := dialer.DialTLS( conn, err := dialer.DialTLS(
context.Background(), context.Background(),
hostPort.String(), hostPort.String(),
dialer.DialerOpts{TLSConfig: &tls.Config{InsecureSkipVerify: true}}, dialer.Opts{TLSConfig: &tls.Config{InsecureSkipVerify: true}},
) // #nosec G402 ) // #nosec G402
die.If(err) die.If(err)
@@ -65,7 +65,7 @@ func printPeerCertificates(certificates []*x509.Certificate) {
fmt.Printf("\tSubject: %s\n", cert.Subject) fmt.Printf("\tSubject: %s\n", cert.Subject)
fmt.Printf("\tIssuer: %s\n", cert.Issuer) fmt.Printf("\tIssuer: %s\n", cert.Issuer)
fmt.Printf("\tDNS Names: %v\n", cert.DNSNames) fmt.Printf("\tDNS Names: %v\n", cert.DNSNames)
fmt.Printf("\tNot Before: %s\n:", cert.NotBefore) fmt.Printf("\tNot Before: %s\n", cert.NotBefore)
fmt.Printf("\tNot After: %s\n", cert.NotAfter) fmt.Printf("\tNot After: %s\n", cert.NotAfter)
} }
} }

View File

@@ -1,5 +1,5 @@
// Package lib contains reusable helpers. This file provides proxy-aware // Package dialer provides proxy-aware dialers for plain TCP and TLS
// dialers for plain TCP and TLS connections using environment variables. // connections using environment variables.
// //
// Supported proxy environment variables (checked case-insensitively): // Supported proxy environment variables (checked case-insensitively):
// - SOCKS5_PROXY (e.g., socks5://user:pass@host:1080) // - SOCKS5_PROXY (e.g., socks5://user:pass@host:1080)
@@ -66,7 +66,7 @@ func BaselineTLSConfig(skipVerify bool, secure bool) (*tls.Config, error) {
var debug = dbg.NewFromEnv() var debug = dbg.NewFromEnv()
// DialerOpts controls creation of proxy-aware dialers. // Opts controls creation of proxy-aware dialers.
// //
// Timeout controls the maximum amount of time spent establishing the // Timeout controls the maximum amount of time spent establishing the
// underlying TCP connection and any proxy handshake. If zero, a // underlying TCP connection and any proxy handshake. If zero, a
@@ -75,7 +75,7 @@ var debug = dbg.NewFromEnv()
// TLSConfig is used by the TLS dialer to configure the TLS handshake to // TLSConfig is used by the TLS dialer to configure the TLS handshake to
// the target endpoint. If TLSConfig.ServerName is empty, it will be set // the target endpoint. If TLSConfig.ServerName is empty, it will be set
// from the host portion of the address passed to DialContext. // from the host portion of the address passed to DialContext.
type DialerOpts struct { type Opts struct {
Timeout time.Duration Timeout time.Duration
TLSConfig *tls.Config TLSConfig *tls.Config
} }
@@ -88,7 +88,7 @@ type ContextDialer interface {
// DialTCP is a convenience helper that dials a TCP connection to address // DialTCP is a convenience helper that dials a TCP connection to address
// using a proxy-aware dialer derived from opts. It honors SOCKS5_PROXY, // using a proxy-aware dialer derived from opts. It honors SOCKS5_PROXY,
// HTTPS_PROXY, and HTTP_PROXY environment variables. // HTTPS_PROXY, and HTTP_PROXY environment variables.
func DialTCP(ctx context.Context, address string, opts DialerOpts) (net.Conn, error) { func DialTCP(ctx context.Context, address string, opts Opts) (net.Conn, error) {
d, err := NewNetDialer(opts) d, err := NewNetDialer(opts)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -100,7 +100,7 @@ func DialTCP(ctx context.Context, address string, opts DialerOpts) (net.Conn, er
// address using a proxy-aware dialer derived from opts. It returns a *tls.Conn. // address using a proxy-aware dialer derived from opts. It returns a *tls.Conn.
// It honors SOCKS5_PROXY, HTTPS_PROXY, and HTTP_PROXY environment variables and // It honors SOCKS5_PROXY, HTTPS_PROXY, and HTTP_PROXY environment variables and
// uses opts.TLSConfig for the handshake (filling ServerName from address if empty). // uses opts.TLSConfig for the handshake (filling ServerName from address if empty).
func DialTLS(ctx context.Context, address string, opts DialerOpts) (*tls.Conn, error) { func DialTLS(ctx context.Context, address string, opts Opts) (*tls.Conn, error) {
d, err := NewTLSDialer(opts) d, err := NewTLSDialer(opts)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -123,7 +123,7 @@ func DialTLS(ctx context.Context, address string, opts DialerOpts) (*tls.Conn, e
// proxies discovered from the environment (SOCKS5_PROXY, HTTPS_PROXY, HTTP_PROXY). // proxies discovered from the environment (SOCKS5_PROXY, HTTPS_PROXY, HTTP_PROXY).
// The returned dialer supports context cancellation for direct and HTTP(S) // The returned dialer supports context cancellation for direct and HTTP(S)
// proxies and applies the configured timeout to connection/proxy handshake. // proxies and applies the configured timeout to connection/proxy handshake.
func NewNetDialer(opts DialerOpts) (ContextDialer, error) { func NewNetDialer(opts Opts) (ContextDialer, error) {
if opts.Timeout <= 0 { if opts.Timeout <= 0 {
opts.Timeout = 30 * time.Second opts.Timeout = 30 * time.Second
} }
@@ -165,7 +165,7 @@ func NewNetDialer(opts DialerOpts) (ContextDialer, error) {
// //
// The returned dialer performs proxy negotiation (if any), then completes a // The returned dialer performs proxy negotiation (if any), then completes a
// TLS handshake to the target using opts.TLSConfig. // TLS handshake to the target using opts.TLSConfig.
func NewTLSDialer(opts DialerOpts) (ContextDialer, error) { func NewTLSDialer(opts Opts) (ContextDialer, error) {
if opts.Timeout <= 0 { if opts.Timeout <= 0 {
opts.Timeout = 30 * time.Second opts.Timeout = 30 * time.Second
} }
@@ -247,7 +247,7 @@ func getProxyURLFromEnv(name string) *url.URL {
// HTTPS_PROXY, and NO_PROXY/no_proxy. // HTTPS_PROXY, and NO_PROXY/no_proxy.
// - Connection and TLS handshake timeouts are derived from opts.Timeout. // - Connection and TLS handshake timeouts are derived from opts.Timeout.
// - For HTTPS targets, opts.TLSConfig is applied to the transport. // - For HTTPS targets, opts.TLSConfig is applied to the transport.
func NewHTTPClient(opts DialerOpts) (*http.Client, error) { func NewHTTPClient(opts Opts) (*http.Client, error) {
if opts.Timeout <= 0 { if opts.Timeout <= 0 {
opts.Timeout = 30 * time.Second opts.Timeout = 30 * time.Second
} }
@@ -422,7 +422,7 @@ func drainHeaders(br *bufio.Reader) error {
} }
// newSOCKS5Dialer builds a context-aware wrapper over the x/net/proxy dialer. // newSOCKS5Dialer builds a context-aware wrapper over the x/net/proxy dialer.
func newSOCKS5Dialer(u *url.URL, opts DialerOpts) (ContextDialer, error) { func newSOCKS5Dialer(u *url.URL, opts Opts) (ContextDialer, error) {
var auth *xproxy.Auth var auth *xproxy.Auth
if u.User != nil { if u.User != nil {
user := u.User.Username() user := u.User.Username()
@@ -468,8 +468,8 @@ func (s *socks5ContextDialer) DialContext(ctx context.Context, network, address
// tlsWrappingDialer performs a TLS handshake over an existing base dialer. // tlsWrappingDialer performs a TLS handshake over an existing base dialer.
type tlsWrappingDialer struct { type tlsWrappingDialer struct {
base ContextDialer base ContextDialer
tcfg *tls.Config tcfg *tls.Config
timeout time.Duration timeout time.Duration
} }

View File

@@ -67,7 +67,7 @@ func (sf *ServerFetcher) String() string {
} }
func (sf *ServerFetcher) GetChain() ([]*x509.Certificate, error) { func (sf *ServerFetcher) GetChain() ([]*x509.Certificate, error) {
opts := dialer.DialerOpts{ opts := dialer.Opts{
TLSConfig: &tls.Config{ TLSConfig: &tls.Config{
InsecureSkipVerify: sf.insecure, // #nosec G402 - no shit sherlock InsecureSkipVerify: sf.insecure, // #nosec G402 - no shit sherlock
RootCAs: sf.roots, RootCAs: sf.roots,

View File

@@ -3,6 +3,7 @@ package lib
import ( import (
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@@ -118,6 +119,8 @@ func IsDigit(b byte) bool {
return b >= '0' && b <= '9' return b >= '0' && b <= '9'
} }
const signedaMask64 = 1<<63 - 1
// ParseDuration parses a duration string into a time.Duration. // ParseDuration parses a duration string into a time.Duration.
// It supports standard units (ns, us/µs, ms, s, m, h) plus extended units: // It supports standard units (ns, us/µs, ms, s, m, h) plus extended units:
// d (days, 24h), w (weeks, 7d), y (years, 365d). // d (days, 24h), w (weeks, 7d), y (years, 365d).
@@ -127,7 +130,7 @@ func IsDigit(b byte) bool {
func ParseDuration(s string) (time.Duration, error) { func ParseDuration(s string) (time.Duration, error) {
s = strings.ToLower(s) // Normalize to lowercase for case-insensitivity. s = strings.ToLower(s) // Normalize to lowercase for case-insensitivity.
if s == "" { if s == "" {
return 0, fmt.Errorf("empty duration string") return 0, errors.New("empty duration string")
} }
var total time.Duration var total time.Duration
@@ -165,23 +168,24 @@ func ParseDuration(s string) (time.Duration, error) {
var d time.Duration var d time.Duration
switch unit { switch unit {
case "ns": case "ns":
d = time.Nanosecond * time.Duration(num) d = time.Nanosecond * time.Duration(num&signedaMask64) // #nosec G115 - masked off
case "us", "µs": case "us", "µs":
d = time.Microsecond * time.Duration(num) d = time.Microsecond * time.Duration(num&signedaMask64) // #nosec G115 - masked off
case "ms": case "ms":
d = time.Millisecond * time.Duration(num) d = time.Millisecond * time.Duration(num&signedaMask64) // #nosec G115 - masked off
case "s": case "s":
d = time.Second * time.Duration(num) d = time.Second * time.Duration(num&signedaMask64) // #nosec G115 - masked off
case "m": case "m":
d = time.Minute * time.Duration(num) d = time.Minute * time.Duration(num&signedaMask64) // #nosec G115 - masked off
case "h": case "h":
d = time.Hour * time.Duration(num) d = time.Hour * time.Duration(num&signedaMask64) // #nosec G115 - masked off
case "d": case "d":
d = 24 * time.Hour * time.Duration(num) d = 24 * time.Hour * time.Duration(num&signedaMask64) // #nosec G115 - masked off
case "w": case "w":
d = 7 * 24 * time.Hour * time.Duration(num) d = 7 * 24 * time.Hour * time.Duration(num&signedaMask64) // #nosec G115 - masked off
case "y": case "y":
d = 365 * 24 * time.Hour * time.Duration(num) // Approximate, non-leap year. // Approximate, non-leap year.
d = 365 * 24 * time.Hour * time.Duration(num&signedaMask64) // #nosec G115 - masked off;
default: default:
return 0, fmt.Errorf("unknown unit %q at position %d", s[unitStart:i], unitStart) return 0, fmt.Errorf("unknown unit %q at position %d", s[unitStart:i], unitStart)
} }