Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e1cb7efbf1 | |||
| 925e0a7124 | |||
| 659f636d01 | |||
| e43c677fba | |||
| 94c55af888 | |||
| ee8e48cd56 | |||
| 11866a3b29 |
@@ -488,6 +488,8 @@ linters:
|
|||||||
linters: [ exhaustive, mnd, revive ]
|
linters: [ exhaustive, mnd, revive ]
|
||||||
- path: 'backoff/backoff_test.go'
|
- path: 'backoff/backoff_test.go'
|
||||||
linters: [ testpackage ]
|
linters: [ testpackage ]
|
||||||
|
- path: "cmd/kgz/main.go"
|
||||||
|
linters: [ unconvert ]
|
||||||
- path: 'dbg/dbg_test.go'
|
- path: 'dbg/dbg_test.go'
|
||||||
linters: [ testpackage ]
|
linters: [ testpackage ]
|
||||||
- path: 'log/logger.go'
|
- path: 'log/logger.go'
|
||||||
|
|||||||
23
CHANGELOG
23
CHANGELOG
@@ -1,5 +1,28 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
|
|
||||||
|
v1.19.0 - 2026-02-12
|
||||||
|
|
||||||
|
Added:
|
||||||
|
- certlib/dump: DisplayCSR function for dumping Certificate Signing Request information.
|
||||||
|
- certlib: MatchKeysCSR function for testing if a CSR's public key matches a private key.
|
||||||
|
|
||||||
|
v1.18.0 - 2025-11-21
|
||||||
|
|
||||||
|
Changed:
|
||||||
|
- disable unconvert for kgz, as various platforms complain about it.
|
||||||
|
|
||||||
|
v1.17.2 - 2025-11-21
|
||||||
|
|
||||||
|
Note: 1.17.2 was a mangled release.
|
||||||
|
|
||||||
|
Changed:
|
||||||
|
- certlib: fix request configs in testdata.
|
||||||
|
|
||||||
|
v1.17.1 - 2025-11-21
|
||||||
|
|
||||||
|
Changed:
|
||||||
|
- certlib: various code cleanups.
|
||||||
|
|
||||||
v1.17.0 - 2025-11-21
|
v1.17.0 - 2025-11-21
|
||||||
|
|
||||||
Added:
|
Added:
|
||||||
|
|||||||
@@ -448,13 +448,13 @@ func encodeCertsToFiles(
|
|||||||
derContent = append(derContent, cert.Raw...)
|
derContent = append(derContent, cert.Raw...)
|
||||||
}
|
}
|
||||||
files = append(files, fileEntry{
|
files = append(files, fileEntry{
|
||||||
name: baseName + ".crt",
|
name: baseName + ".cer",
|
||||||
content: derContent,
|
content: derContent,
|
||||||
})
|
})
|
||||||
} else if len(certs) > 0 {
|
} else if len(certs) > 0 {
|
||||||
// Individual DER file (should only have one cert)
|
// Individual DER file (should only have one cert)
|
||||||
files = append(files, fileEntry{
|
files = append(files, fileEntry{
|
||||||
name: baseName + ".crt",
|
name: baseName + ".cer",
|
||||||
content: certs[0].Raw,
|
content: certs[0].Raw,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -472,17 +472,17 @@ func encodeCertsToFiles(
|
|||||||
derContent = append(derContent, cert.Raw...)
|
derContent = append(derContent, cert.Raw...)
|
||||||
}
|
}
|
||||||
files = append(files, fileEntry{
|
files = append(files, fileEntry{
|
||||||
name: baseName + ".crt",
|
name: baseName + ".cer",
|
||||||
content: derContent,
|
content: derContent,
|
||||||
})
|
})
|
||||||
} else if len(certs) > 0 {
|
} else if len(certs) > 0 {
|
||||||
files = append(files, fileEntry{
|
files = append(files, fileEntry{
|
||||||
name: baseName + ".crt",
|
name: baseName + ".cer",
|
||||||
content: certs[0].Raw,
|
content: certs[0].Raw,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported encoding: %s (must be 'pem', 'der', or 'both')", encoding)
|
return nil, fmt.Errorf("unsupported encoding: %s (must be 'pem', 'der', 'both', 'crt', 'pemcrt')", encoding)
|
||||||
}
|
}
|
||||||
|
|
||||||
return files, nil
|
return files, nil
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ type Subject struct {
|
|||||||
Province string `yaml:"province"`
|
Province string `yaml:"province"`
|
||||||
Organization string `yaml:"organization"`
|
Organization string `yaml:"organization"`
|
||||||
OrganizationalUnit string `yaml:"organizational_unit"`
|
OrganizationalUnit string `yaml:"organizational_unit"`
|
||||||
Email string `yaml:"email"`
|
Email []string `yaml:"email"`
|
||||||
DNSNames []string `yaml:"dns"`
|
DNSNames []string `yaml:"dns"`
|
||||||
IPAddresses []string `yaml:"ips"`
|
IPAddresses []string `yaml:"ips"`
|
||||||
}
|
}
|
||||||
@@ -92,14 +92,11 @@ func (cs CertificateRequest) Request(priv crypto.PrivateKey) (*x509.CertificateR
|
|||||||
PublicKeyAlgorithm: 0,
|
PublicKeyAlgorithm: 0,
|
||||||
PublicKey: getPublic(priv),
|
PublicKey: getPublic(priv),
|
||||||
Subject: subject,
|
Subject: subject,
|
||||||
|
EmailAddresses: cs.Subject.Email,
|
||||||
DNSNames: cs.Subject.DNSNames,
|
DNSNames: cs.Subject.DNSNames,
|
||||||
IPAddresses: ipAddresses,
|
IPAddresses: ipAddresses,
|
||||||
}
|
}
|
||||||
|
|
||||||
if cs.Subject.Email != "" {
|
|
||||||
req.EmailAddresses = []string{cs.Subject.Email}
|
|
||||||
}
|
|
||||||
|
|
||||||
reqBytes, err := x509.CreateCertificateRequest(rand.Reader, req, priv)
|
reqBytes, err := x509.CreateCertificateRequest(rand.Reader, req, priv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create certificate request: %w", err)
|
return nil, fmt.Errorf("failed to create certificate request: %w", err)
|
||||||
@@ -130,7 +127,7 @@ func (cs CertificateRequest) Generate() (crypto.PrivateKey, *x509.CertificateReq
|
|||||||
type Profile struct {
|
type Profile struct {
|
||||||
IsCA bool `yaml:"is_ca"`
|
IsCA bool `yaml:"is_ca"`
|
||||||
PathLen int `yaml:"path_len"`
|
PathLen int `yaml:"path_len"`
|
||||||
KeyUse string `yaml:"key_uses"`
|
KeyUse []string `yaml:"key_uses"`
|
||||||
ExtKeyUsages []string `yaml:"ext_key_usages"`
|
ExtKeyUsages []string `yaml:"ext_key_usages"`
|
||||||
Expiry string `yaml:"expiry"`
|
Expiry string `yaml:"expiry"`
|
||||||
}
|
}
|
||||||
@@ -161,15 +158,17 @@ func (p Profile) templateFromRequest(req *x509.CertificateRequest) (*x509.Certif
|
|||||||
IPAddresses: req.IPAddresses,
|
IPAddresses: req.IPAddresses,
|
||||||
}
|
}
|
||||||
|
|
||||||
var ok bool
|
for _, sku := range p.KeyUse {
|
||||||
certTemplate.KeyUsage, ok = keyUsageStrings[p.KeyUse]
|
ku, ok := keyUsageStrings[sku]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("invalid key usage: %s", p.KeyUse)
|
return nil, fmt.Errorf("invalid key usage: %s", p.KeyUse)
|
||||||
|
}
|
||||||
|
|
||||||
|
certTemplate.KeyUsage |= ku
|
||||||
}
|
}
|
||||||
|
|
||||||
var eku x509.ExtKeyUsage
|
|
||||||
for _, extKeyUsage := range p.ExtKeyUsages {
|
for _, extKeyUsage := range p.ExtKeyUsages {
|
||||||
eku, ok = extKeyUsageStrings[extKeyUsage]
|
eku, ok := extKeyUsageStrings[extKeyUsage]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("invalid extended key usage: %s", extKeyUsage)
|
return nil, fmt.Errorf("invalid extended key usage: %s", extKeyUsage)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,6 +165,28 @@ func certPublic(cert *x509.Certificate) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func csrPublic(csr *x509.CertificateRequest) string {
|
||||||
|
switch pub := csr.PublicKey.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
return fmt.Sprintf("RSA-%d", pub.N.BitLen())
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
switch pub.Curve {
|
||||||
|
case elliptic.P256():
|
||||||
|
return "ECDSA-prime256v1"
|
||||||
|
case elliptic.P384():
|
||||||
|
return "ECDSA-secp384r1"
|
||||||
|
case elliptic.P521():
|
||||||
|
return "ECDSA-secp521r1"
|
||||||
|
default:
|
||||||
|
return "ECDSA (unknown curve)"
|
||||||
|
}
|
||||||
|
case *dsa.PublicKey:
|
||||||
|
return "DSA"
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func DisplayName(name pkix.Name) string {
|
func DisplayName(name pkix.Name) string {
|
||||||
var ns []string
|
var ns []string
|
||||||
|
|
||||||
@@ -333,3 +355,36 @@ func DisplayCert(w io.Writer, cert *x509.Certificate, showHash bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DisplayCSR(w io.Writer, csr *x509.CertificateRequest, showHash bool) {
|
||||||
|
fmt.Fprintln(w, "CERTIFICATE REQUEST")
|
||||||
|
if showHash {
|
||||||
|
fmt.Fprintln(w, wrap(fmt.Sprintf("SHA256: %x", sha256.Sum256(csr.Raw)), 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(w, wrap("Subject: "+DisplayName(csr.Subject), 0))
|
||||||
|
fmt.Fprintf(w, "\tSignature algorithm: %s / %s\n", sigAlgoPK(csr.SignatureAlgorithm),
|
||||||
|
sigAlgoHash(csr.SignatureAlgorithm))
|
||||||
|
fmt.Fprintln(w, "Details:")
|
||||||
|
wrapPrint("Public key: "+csrPublic(csr), 1)
|
||||||
|
|
||||||
|
validNames := make([]string, 0, len(csr.DNSNames)+len(csr.EmailAddresses)+len(csr.IPAddresses)+len(csr.URIs))
|
||||||
|
for i := range csr.DNSNames {
|
||||||
|
validNames = append(validNames, "dns:"+csr.DNSNames[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range csr.EmailAddresses {
|
||||||
|
validNames = append(validNames, "email:"+csr.EmailAddresses[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range csr.IPAddresses {
|
||||||
|
validNames = append(validNames, "ip:"+csr.IPAddresses[i].String())
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range csr.URIs {
|
||||||
|
validNames = append(validNames, "uri:"+csr.URIs[i].String())
|
||||||
|
}
|
||||||
|
|
||||||
|
sans := fmt.Sprintf("SANs (%d): %s\n", len(validNames), strings.Join(validNames, ", "))
|
||||||
|
wrapPrint(sans, 1)
|
||||||
|
}
|
||||||
|
|||||||
@@ -133,3 +133,48 @@ func MatchKeys(cert *x509.Certificate, priv crypto.Signer) (bool, string) {
|
|||||||
return false, fmt.Sprintf("unrecognised private key type: %T", priv.Public())
|
return false, fmt.Sprintf("unrecognised private key type: %T", priv.Public())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MatchKeysCSR determines whether the CSR's public key matches the given private key.
|
||||||
|
// It returns true if they match; otherwise, it returns false and a human-friendly reason.
|
||||||
|
func MatchKeysCSR(csr *x509.CertificateRequest, priv crypto.Signer) (bool, string) {
|
||||||
|
switch keyPub := priv.Public().(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
switch csrPub := csr.PublicKey.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
if matchRSA(csrPub, 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 CSR public key type: %T", csr.PublicKey)
|
||||||
|
}
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
switch csrPub := csr.PublicKey.(type) {
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
if matchECDSA(csrPub, keyPub) {
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
// Determine a more precise reason
|
||||||
|
kc := getECCurve(keyPub)
|
||||||
|
cc := getECCurve(csrPub)
|
||||||
|
if kc == curveInvalid {
|
||||||
|
return false, "invalid private key curve"
|
||||||
|
}
|
||||||
|
if cc == curveRSA {
|
||||||
|
return false, "private key is EC, CSR 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, CSR is RSA"
|
||||||
|
default:
|
||||||
|
return false, fmt.Sprintf("unsupported CSR public key type: %T", csr.PublicKey)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false, fmt.Sprintf("unrecognised private key type: %T", priv.Public())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
3
certlib/testdata/ec-ca.yaml
vendored
3
certlib/testdata/ec-ca.yaml
vendored
@@ -9,5 +9,6 @@ subject:
|
|||||||
profile:
|
profile:
|
||||||
is_ca: true
|
is_ca: true
|
||||||
path_len: 3
|
path_len: 3
|
||||||
key_uses: cert sign
|
key_uses:
|
||||||
|
- cert sign
|
||||||
expiry: 20y
|
expiry: 20y
|
||||||
|
|||||||
3
certlib/testdata/rsa-ca.yaml
vendored
3
certlib/testdata/rsa-ca.yaml
vendored
@@ -9,5 +9,6 @@ subject:
|
|||||||
profile:
|
profile:
|
||||||
is_ca: true
|
is_ca: true
|
||||||
path_len: 3
|
path_len: 3
|
||||||
key_uses: cert sign
|
key_uses:
|
||||||
|
- cert sign
|
||||||
expiry: 20y
|
expiry: 20y
|
||||||
|
|||||||
Reference in New Issue
Block a user