TLS Certificate Public Key Too Small
A TLS leaf certificate uses RSA below 2048 bits or an elliptic curve below 256 bits.
- Why It Matters
- Undersized public keys provide inadequate security strength and may permit certificate impersonation as cryptanalytic capability improves.
- How It Detects
- Reads the leaf public key from loopback TLS handshakes and measures RSA modulus or elliptic curve size.
- How to Fix
- Replace the certificate and private key with RSA 2048 bits or stronger, or an approved EC key of at least 256 bits.
- How to Verify
Inspect the renewed leaf certificate key size on ports 443, 465, 993, 995, 8443, and 10000. STARTTLS is out of scope.- CVSS v3.1 Vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N- Control Category
- Cryptography and TLS: CIS Controls v8 3; NIST CSF 2.0 PR.DS
- Fix Effort
- Medium
- CIS Controls v8 Safeguard
- 3.10
- NIST CSF 2.0 Subcategory
- PR.DS-02
- OWASP WSTG v4.2
- WSTG-CRYP-01
- CWE
- CWE-326
- OWASP Top 10
- A02:2021
- NIST SP 800-53 Rev. 5
- SC-12, SC-13
- References
- NIST SP 800-131A Rev. 2, Transitioning the Use of Cryptographic Algorithms and Key Lengths
Source Code
The complete file that defines this check. Run it with openvapt scan --only NET-TLS-005, or read it offline with openvapt explain NET-TLS-005.
internal/checks/network/tls_depth.go · Lines 1-116View on GitHub at This Commit
| 1 | package network |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "crypto/tls" |
| 6 | "fmt" |
| 7 | "strings" |
| 8 | |
| 9 | "github.com/openvapt/openvapt/internal/check" |
| 10 | "github.com/openvapt/openvapt/internal/sys" |
| 11 | ) |
| 12 | |
| 13 | func init() { |
| 14 | check.Register(&check.Check{ |
| 15 | ID: "NET-TLS-004", |
| 16 | Title: "Weak TLS 1.2 Cipher Suites Accepted", |
| 17 | Domain: check.DomainNetwork, |
| 18 | Severity: check.Medium, |
| 19 | Category: check.CatCrypto, |
| 20 | Effort: check.EffortLow, |
| 21 | CVSS: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N", |
| 22 | Summary: "A local TLS service accepts an RC4, 3DES, or CBC with SHA-1 cipher suite under TLS 1.2.", |
| 23 | Rationale: "Obsolete cipher constructions lack modern authenticated encryption and are prohibited by current TLS hardening guidance.", |
| 24 | Detection: "Offers restricted TLS 1.2 cipher lists to loopback services. A successful handshake proves the weak suite is accepted. Go does not implement NULL, EXPORT, or anonymous suites, so those cannot be actively offered and remain a documented limitation.", |
| 25 | Fix: "Allow only modern AEAD suites for TLS 1.2 and prefer TLS 1.3. Remove RC4, 3DES, NULL, EXPORT, anonymous, and CBC with SHA-1 suites. Caution: very old clients and devices that support only those suites stop connecting; check the access logs for them first.", |
| 26 | Verify: "Use an approved local TLS scanner and confirm each prohibited TLS 1.2 suite is rejected. Implicit TLS mail ports 465, 993, and 995 are included; STARTTLS is out of scope.", |
| 27 | Refs: refTLS, |
| 28 | Standards: check.Standards{CWE: []string{"CWE-326", "CWE-327"}, OWASP: []string{"A02:2021"}, WSTG: []string{"WSTG-CRYP-01"}, ASVS: []string{"9.1.3"}, NIST: []string{"SC-8", "SC-13"}}, |
| 29 | Run: func(ctx context.Context, s sys.System) []check.Result { |
| 30 | targets := tlsTargets(s) |
| 31 | if len(targets) == 0 { |
| 32 | return []check.Result{check.SkipR("TLS", "No local implicit TLS service was found")} |
| 33 | } |
| 34 | weak := []uint16{tls.TLS_RSA_WITH_RC4_128_SHA, tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, tls.TLS_RSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA} |
| 35 | var out []check.Result |
| 36 | for _, t := range targets { |
| 37 | target := t[1] + " on port " + t[0] |
| 38 | accepted := []string{} |
| 39 | for _, suite := range weak { |
| 40 | if info, err := s.TLSWithCiphers(ctx, t[0], t[1], []uint16{suite}); err == nil { |
| 41 | accepted = append(accepted, tls.CipherSuiteName(info.Cipher)) |
| 42 | } |
| 43 | } |
| 44 | if len(accepted) > 0 { |
| 45 | out = append(out, check.FailR(target, "Weak TLS 1.2 cipher suite accepted", accepted...)) |
| 46 | } else { |
| 47 | out = append(out, check.PassR(target, "Testable weak TLS 1.2 cipher suites were rejected")) |
| 48 | } |
| 49 | } |
| 50 | return out |
| 51 | }}) |
| 52 | check.Register(&check.Check{ |
| 53 | ID: "NET-TLS-005", |
| 54 | Title: "TLS Certificate Public Key Too Small", |
| 55 | Domain: check.DomainNetwork, |
| 56 | Severity: check.Medium, |
| 57 | Category: check.CatCrypto, |
| 58 | Effort: check.EffortMedium, |
| 59 | CVSS: "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N", |
| 60 | Summary: "A TLS leaf certificate uses RSA below 2048 bits or an elliptic curve below 256 bits.", |
| 61 | Rationale: "Undersized public keys provide inadequate security strength and may permit certificate impersonation as cryptanalytic capability improves.", |
| 62 | Detection: "Reads the leaf public key from loopback TLS handshakes and measures RSA modulus or elliptic curve size.", |
| 63 | Fix: "Replace the certificate and private key with RSA 2048 bits or stronger, or an approved EC key of at least 256 bits.", |
| 64 | Verify: "Inspect the renewed leaf certificate key size on ports 443, 465, 993, 995, 8443, and 10000. STARTTLS is out of scope.", |
| 65 | Refs: []check.Ref{{Title: "NIST SP 800-131A Rev. 2, Transitioning the Use of Cryptographic Algorithms and Key Lengths", URL: "https://csrc.nist.gov/pubs/sp/800/131/a/r2/final"}}, |
| 66 | Standards: check.Standards{CWE: []string{"CWE-326"}, OWASP: []string{"A02:2021"}, WSTG: []string{"WSTG-CRYP-01"}, NIST: []string{"SC-12", "SC-13"}}, |
| 67 | Run: certDepthRun(func(i sys.TLSInfo) bool { |
| 68 | return strings.EqualFold(i.PublicKeyAlgorithm, "RSA") && i.PublicKeyBits < 2048 || (strings.EqualFold(i.PublicKeyAlgorithm, "ECDSA") || strings.Contains(strings.ToLower(i.PublicKeyAlgorithm), "elliptic")) && i.PublicKeyBits < 256 |
| 69 | }, "Leaf certificate public key is too small")}) |
| 70 | check.Register(&check.Check{ |
| 71 | ID: "NET-TLS-006", |
| 72 | Title: "SHA-1 Signed TLS Leaf Certificate", |
| 73 | Domain: check.DomainNetwork, |
| 74 | Severity: check.Medium, |
| 75 | Category: check.CatCrypto, |
| 76 | Effort: check.EffortMedium, |
| 77 | CVSS: "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N", |
| 78 | Summary: "A TLS leaf certificate is signed using SHA-1.", |
| 79 | Rationale: "SHA-1 collision attacks invalidate its use for digital signatures and modern clients reject SHA-1 certificates.", |
| 80 | Detection: "Reads the leaf certificate signature algorithm from loopback implicit TLS handshakes and flags SHA-1.", |
| 81 | Fix: "Reissue the certificate with SHA-256 or stronger and deploy the complete updated chain.", |
| 82 | Verify: "Inspect the signature algorithm on each implicit TLS service. STARTTLS is out of scope.", |
| 83 | Refs: []check.Ref{{Title: "NIST Transitioning Away from SHA-1", URL: "https://csrc.nist.gov/News/2022/nist-transitioning-away-from-sha-1-for-all-apps"}}, |
| 84 | Standards: check.Standards{CWE: []string{"CWE-327"}, OWASP: []string{"A02:2021"}, WSTG: []string{"WSTG-CRYP-01"}, NIST: []string{"SC-12", "SC-13"}}, |
| 85 | Run: certDepthRun(func(i sys.TLSInfo) bool { |
| 86 | return strings.Contains(strings.ToLower(i.SignatureAlgorithm), "sha-1") || strings.Contains(strings.ToLower(i.SignatureAlgorithm), "sha1") |
| 87 | }, "Leaf certificate uses a SHA-1 signature")}) |
| 88 | } |
| 89 | |
| 90 | func certDepthRun(bad func(sys.TLSInfo) bool, msg string) func(context.Context, sys.System) []check.Result { |
| 91 | return func(ctx context.Context, s sys.System) []check.Result { |
| 92 | targets := tlsTargets(s) |
| 93 | if len(targets) == 0 { |
| 94 | return []check.Result{check.SkipR("TLS", "No local implicit TLS service was found")} |
| 95 | } |
| 96 | var out []check.Result |
| 97 | for _, t := range targets { |
| 98 | target := t[1] + " on port " + t[0] |
| 99 | i, err := s.TLS(ctx, t[0], t[1], tls.VersionTLS12, tls.VersionTLS13) |
| 100 | if err != nil { |
| 101 | out = append(out, check.IncompleteR(target, "TLS certificate could not be read")) |
| 102 | continue |
| 103 | } |
| 104 | if i.PublicKeyBits == 0 || i.SignatureAlgorithm == "" { |
| 105 | out = append(out, check.IncompleteR(target, "Certificate key or signature metadata was unavailable")) |
| 106 | continue |
| 107 | } |
| 108 | if bad(i) { |
| 109 | out = append(out, check.FailR(target, msg, fmt.Sprintf("key %s %d bits, signature %s", i.PublicKeyAlgorithm, i.PublicKeyBits, i.SignatureAlgorithm))) |
| 110 | } else { |
| 111 | out = append(out, check.PassR(target, "Certificate key strength and signature metadata passed this check")) |
| 112 | } |
| 113 | } |
| 114 | return out |
| 115 | } |
| 116 | } |
Also available as plain Markdown for notes, scripts and AI tools. Same facts, same source, no styling.
Cite This Page
If this page helped your work, please cite it. The version and web address are filled in; the access date is today's date in your browser. Every other style, and when credit is required, is on How to Cite OpenVAPT.
OpenVAPT. (2026). NET-TLS-005 TLS Certificate Public Key Too Small (Version 0.1.0-alpha.6). OpenVAPT Check Catalogue. Retrieved October 16, 2026, from https://openvapt.com/checks/NET-TLS-005.html
In text: (OpenVAPT, 2026). Accessed: the date shown is today's date from your own browser (the site's build date without JavaScript); change it to the day you read the page.
@misc{openvapt_net_tls_005,
author = {{OpenVAPT}},
title = {{NET-TLS-005 TLS Certificate Public Key Too Small}},
year = {2026},
howpublished = {\url{https://openvapt.com/checks/NET-TLS-005.html}},
note = {OpenVAPT Check Catalogue, version 0.1.0-alpha.6. Accessed 16 October 2026}
}
Accessed: the date shown is today's date from your own browser (the site's build date without JavaScript); change it to the day you read the page.
Citing OpenVAPT does not mean OpenVAPT endorses, approves or has reviewed your work.