Add CAA checker, based on TLS observations
Some checks are pending
continuous-integration/drone/push Build is pending

This commit is contained in:
nemunaire 2026-04-23 02:50:46 +07:00
commit 0d35040a40
7 changed files with 601 additions and 159 deletions

34
checkers/caa.go Normal file
View file

@ -0,0 +1,34 @@
// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2026 happyDomain
// Authors: Pierre-Olivier Mercier, et al.
//
// This program is offered under a commercial and under the AGPL license.
// For commercial licensing, contact us at <contact@happydomain.org>.
//
// For AGPL licensing:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package checkers
import (
caa "git.happydns.org/checker-caa/checker"
sdk "git.happydns.org/checker-sdk-go/checker"
"git.happydns.org/happyDomain/internal/checker"
)
func init() {
prvd := caa.Provider()
checker.RegisterObservationProvider(prvd)
checker.RegisterExternalizableChecker(prvd.(sdk.CheckerDefinitionProvider).Definition())
}

View file

@ -27,5 +27,6 @@ package main
//go:generate go run tools/gen_rr_typescript.go web/src/lib/dns_rr.ts
//go:generate go run tools/gen_service_specs.go -o web/src/lib/services_specs.ts
//go:generate go run tools/gen_dns_type_mapping.go -o internal/usecase/service_specs_dns_types.go
//go:generate go run tools/gen_caa_issuers.go -o web/src/lib/services/caa-issuers.json https://ccadb.my.salesforce-sites.com/ccadb/AllCAAIdentifiersReportCSVV2
//go:generate swag init --parseDependency --exclude internal/api-admin/ --generalInfo internal/api/route/route.go
//go:generate swag init --parseDependency --output docs-admin --exclude internal/api/ --generalInfo internal/api-admin/route/route.go

1
go.mod
View file

@ -8,6 +8,7 @@ require (
git.happydns.org/checker-alias v0.1.0
git.happydns.org/checker-authoritative-consistency v0.1.0
git.happydns.org/checker-blacklist v0.1.0
git.happydns.org/checker-caa v0.1.0
git.happydns.org/checker-dane v0.1.3
git.happydns.org/checker-dangling v0.1.0
git.happydns.org/checker-dav v0.1.0

2
go.sum
View file

@ -14,6 +14,8 @@ git.happydns.org/checker-authoritative-consistency v0.1.0 h1:+0XvJFC7tFVf0Dgruew
git.happydns.org/checker-authoritative-consistency v0.1.0/go.mod h1:hPxEDSyrPq+KY9YU5QoZ1btecw/cU/Miouuacaz4wzk=
git.happydns.org/checker-blacklist v0.1.0 h1:IV44Lxnw0dLBhoyAkAlq9A+hTB5B4RF4vLiW+nX21gg=
git.happydns.org/checker-blacklist v0.1.0/go.mod h1:DRHkpULz8F6dKm0LUErAAQln0x8XByg+/UxbUY46oZk=
git.happydns.org/checker-caa v0.1.0 h1:L0kg9dqdJqmjaPrgbLtBvgEE6+e+7EVSSRPB5pIzNIQ=
git.happydns.org/checker-caa v0.1.0/go.mod h1:7ecPoFRYT0+Fl5DG17Xvz9Xh2alwgEpSSaE2rp0EcT0=
git.happydns.org/checker-dane v0.1.3 h1:9VpQ4FrWJE/O6MZ08FCk1vmHsr3u5V7478als9Y4jl8=
git.happydns.org/checker-dane v0.1.3/go.mod h1:md5SQA8M1QGq9MoXe3QVV+m55I+r8lU4iYx5KzvkbII=
git.happydns.org/checker-dangling v0.1.0 h1:gZVyHAKG2U1FXBt7cPnZsr45JQWZ21jlThKhHckb+i8=

405
tools/gen_caa_issuers.go Normal file
View file

@ -0,0 +1,405 @@
// This file is part of the happyDomain (R) project.
// Copyright (c) 2022-2026 happyDomain
// Authors: Pierre-Olivier Mercier, et al.
//
// This program is offered under a commercial and under the AGPL license.
// For commercial licensing, contact us at <contact@happydomain.org>.
//
// For AGPL licensing:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//go:build ignore
// +build ignore
package main
import (
"encoding/csv"
"encoding/json"
"flag"
"fmt"
"io"
"net/http"
"os"
"sort"
"strings"
)
const (
subjectColumn = "Subject"
domainColumn = "Recognized CAA Domains"
)
// row represents a single CCADB intermediate and the CAA domains it recognizes.
type row struct {
owner string
domains []string
}
// score tracks how well a candidate owner matches a CAA domain across all
// rows that mention that domain.
type score struct {
rowCount int
minSize int
nameMatch bool
}
func main() {
output := flag.String("o", "", "path to write the generated JSON file")
flag.Parse()
if *output == "" {
fatal("missing required -o flag")
}
if flag.NArg() < 1 {
fatal("missing CCADB CSV URL (first positional argument)")
}
url := flag.Arg(0)
rows, err := fetchAndParse(url)
if err != nil {
fatal(err.Error())
}
mapping := buildDomainToOwner(rows)
if err := writeJSON(*output, mapping); err != nil {
fatal(err.Error())
}
}
func fetchAndParse(url string) ([]row, error) {
resp, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("GET %s: %w", url, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("GET %s: unexpected status %s", url, resp.Status)
}
return parseCSV(resp.Body)
}
func parseCSV(r io.Reader) ([]row, error) {
reader := csv.NewReader(r)
reader.FieldsPerRecord = -1
header, err := reader.Read()
if err != nil {
return nil, fmt.Errorf("read header: %w", err)
}
subjectIdx := indexOf(header, subjectColumn)
domainIdx := indexOf(header, domainColumn)
if subjectIdx < 0 {
return nil, fmt.Errorf("column %q not found in CSV header", subjectColumn)
}
if domainIdx < 0 {
return nil, fmt.Errorf("column %q not found in CSV header", domainColumn)
}
var rows []row
for {
record, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, fmt.Errorf("read row: %w", err)
}
if subjectIdx >= len(record) || domainIdx >= len(record) {
continue
}
owner := extractOrganization(record[subjectIdx])
if owner == "" {
continue
}
domains := splitDomains(record[domainIdx])
if len(domains) == 0 {
continue
}
rows = append(rows, row{owner: owner, domains: domains})
}
return rows, nil
}
// buildDomainToOwner inverts the CCADB rows into a CAA-domain → owner mapping.
//
// For each CAA identifier, the "authoritative" owner is selected by:
// 1. Preferring owners whose name contains a significant label of the CAA
// domain (e.g. "digicert.com" prefers "DigiCert, Inc." over cross-signed
// subordinates that also list digicert.com in their recognized set).
// 2. Then highest row count: real root CAs have many intermediates all
// recognizing their own identifier.
// 3. Then smallest minimum Recognized-CAA-Domains set: roots typically
// recognize just their own identifier, while subordinates inherit larger
// sets from cross-signing parents.
// 4. Alphabetical for determinism.
//
// Owner names are grouped case-insensitively to collapse CCADB casing
// inconsistencies (e.g. "Cloudflare, Inc." vs "CLOUDFLARE, INC."), preferring
// the variant with the fewest all-caps words.
func buildDomainToOwner(rows []row) map[string]string {
canonical := canonicalOwners(rows)
scores := map[string]map[string]*score{}
for _, r := range rows {
owner := canonical[strings.ToLower(r.owner)]
size := len(r.domains)
for _, dn := range r.domains {
if _, ok := scores[dn]; !ok {
scores[dn] = map[string]*score{}
}
s, ok := scores[dn][owner]
if !ok {
s = &score{minSize: size, nameMatch: ownerMatchesDomain(owner, dn)}
scores[dn][owner] = s
}
s.rowCount++
if size < s.minSize {
s.minSize = size
}
}
}
out := make(map[string]string, len(scores))
for dn, byOwner := range scores {
var bestOwner string
var best *score
for owner, s := range byOwner {
if best == nil || scoreBetter(s, owner, best, bestOwner) {
best = s
bestOwner = owner
}
}
out[dn] = bestOwner
}
return out
}
func scoreBetter(a *score, ao string, b *score, bo string) bool {
if a.nameMatch != b.nameMatch {
return a.nameMatch
}
if a.rowCount != b.rowCount {
return a.rowCount > b.rowCount
}
if a.minSize != b.minSize {
return a.minSize < b.minSize
}
return ao < bo
}
// genericDomainLabels are labels that appear in CAA domains but don't identify
// a specific CA brand (e.g. "pki.goog" is Google, not "pki"). Anything shorter
// than 3 characters (TLDs) is also skipped.
var genericDomainLabels = map[string]bool{
"com": true, "net": true, "org": true, "gov": true, "edu": true,
"co": true,
"pki": true, "tls": true, "ssl": true, "www": true, "eca": true,
"publicca": true, "epki": true, "cert": true, "trust": true,
"certificate": true, "ca": true,
}
// ownerMatchesDomain returns true if a significant label of the CAA domain
// appears as a substring of the owner name (alphanumeric-only, lowercased).
// Used to prefer self-referential owners (e.g. "DigiCert" for "digicert.com")
// over cross-signed subordinates that also list the domain.
func ownerMatchesDomain(owner, caaDomain string) bool {
normName := alphaNumLower(owner)
for _, label := range strings.Split(caaDomain, ".") {
label = strings.ToLower(label)
if len(label) < 3 || genericDomainLabels[label] {
continue
}
if strings.Contains(normName, label) {
return true
}
}
return false
}
func alphaNumLower(s string) string {
var b strings.Builder
b.Grow(len(s))
for _, r := range s {
switch {
case r >= 'a' && r <= 'z':
b.WriteRune(r)
case r >= 'A' && r <= 'Z':
b.WriteRune(r + ('a' - 'A'))
case r >= '0' && r <= '9':
b.WriteRune(r)
}
}
return b.String()
}
// canonicalOwners returns a map from lowercased owner name to the preferred
// display variant. Preference: fewer all-caps words, then lexicographically
// smallest (for determinism).
func canonicalOwners(rows []row) map[string]string {
variants := map[string]map[string]struct{}{}
for _, r := range rows {
key := strings.ToLower(r.owner)
if _, ok := variants[key]; !ok {
variants[key] = map[string]struct{}{}
}
variants[key][r.owner] = struct{}{}
}
out := make(map[string]string, len(variants))
for key, vs := range variants {
picks := make([]string, 0, len(vs))
for v := range vs {
picks = append(picks, v)
}
sort.Slice(picks, func(i, j int) bool {
ai, aj := allCapsWords(picks[i]), allCapsWords(picks[j])
if ai != aj {
return ai < aj
}
return picks[i] < picks[j]
})
out[key] = picks[0]
}
return out
}
// allCapsWords counts words (whitespace-delimited) that contain at least one
// letter and are entirely uppercase — a proxy for "ALL CAPS" shouting that we
// want to avoid when choosing a canonical display form.
func allCapsWords(s string) int {
n := 0
for _, w := range strings.Fields(s) {
hasLetter := false
allUpper := true
for _, r := range w {
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') {
hasLetter = true
if r >= 'a' && r <= 'z' {
allUpper = false
}
}
}
if hasLetter && allUpper {
n++
}
}
return n
}
func splitDomains(cell string) []string {
var out []string
for _, dn := range strings.FieldsFunc(cell, func(r rune) bool {
return r == ',' || r == ' ' || r == '\t' || r == '\n' || r == '\r'
}) {
dn = strings.ToLower(strings.TrimSpace(dn))
if isDomainName(dn) {
out = append(out, dn)
}
}
return out
}
// isDomainName filters free-form text leaking from the CCADB "Recognized CAA
// Domains" cell (tokens like "None", "N/A", "Comma-separated", "list.", or
// sentence fragments). Requires at least two non-empty DNS-like labels and a
// TLD of at least two letters.
func isDomainName(s string) bool {
labels := strings.Split(s, ".")
if len(labels) < 2 {
return false
}
for _, l := range labels {
if l == "" || !isDomainLabel(l) {
return false
}
}
tld := labels[len(labels)-1]
if len(tld) < 2 {
return false
}
for _, r := range tld {
if r < 'a' || r > 'z' {
return false
}
}
return true
}
func isDomainLabel(l string) bool {
if l[0] == '-' || l[len(l)-1] == '-' {
return false
}
for _, r := range l {
switch {
case r >= 'a' && r <= 'z':
case r >= '0' && r <= '9':
case r == '-':
default:
return false
}
}
return true
}
func writeJSON(path string, data map[string]string) error {
buf, err := json.MarshalIndent(data, "", " ")
if err != nil {
return fmt.Errorf("marshal JSON: %w", err)
}
buf = append(buf, '\n')
tmp := path + ".tmp"
if err := os.WriteFile(tmp, buf, 0o644); err != nil {
return fmt.Errorf("write %s: %w", tmp, err)
}
if err := os.Rename(tmp, path); err != nil {
return fmt.Errorf("rename %s -> %s: %w", tmp, path, err)
}
return nil
}
// extractOrganization returns the O= (organization) value from an RFC-4514-ish
// DN string as provided by CCADB (fields separated by "; ").
func extractOrganization(subject string) string {
for _, field := range strings.Split(subject, "; ") {
field = strings.TrimSpace(field)
if v, ok := strings.CutPrefix(field, "O="); ok {
return strings.TrimSpace(v)
}
}
return ""
}
func indexOf(header []string, name string) int {
for i, h := range header {
if strings.TrimSpace(h) == name {
return i
}
}
return -1
}
func fatal(msg string) {
fmt.Fprintln(os.Stderr, "gen_caa_issuers: "+msg)
os.Exit(1)
}

View file

@ -0,0 +1,146 @@
{
"1and1.digitalcertvalidation.com": "DigiCert, Inc.",
"accv.es": "ACCV",
"acme.jprs.jp": "Japan Registry Services Co., Ltd.",
"acme.trust.telia.com": "Telia Company AB",
"actalis.it": "Actalis S.p.A.",
"admin.ch": "Swiss Government PKI",
"affirmtrust.com": "AffirmTrust",
"almostfreessl.com": "SSLCom Group ltd.",
"amazon.com": "Amazon",
"amazonaws.com": "DigiCert, Inc.",
"amazontrust.com": "DigiCert, Inc.",
"anf.es": "ANF Autoridad de Certificacion",
"aoc.cat": "Agencia Catalana de Certificacio (NIF Q-0801176-I)",
"atos.net": "Atos",
"aws.amazon.com": "Amazon",
"awstrust.com": "DigiCert, Inc.",
"bjca.cn": "BEIJING CERTIFICATE AUTHORITY",
"buypass.com": "Buypass AS-983163327",
"buypass.no": "Buypass AS-983163327",
"camerfirma.com": "AC Camerfirma S.A.",
"certainly.com": "Certainly",
"certicamara.com": "CERTICAMARA S.A",
"certifyid.com": "WISeKey",
"certigna.com": "Certigna",
"certigna.fr": "Certigna",
"certsign.ro": "CERTSIGN SA",
"certum.eu": "Asseco Data Systems S.A.",
"certum.pl": "Asseco Data Systems S.A.",
"cfca.com.cn": "China Financial Certification Authority",
"cisco.com": "Cisco Systems",
"comodo.com": "COMODO CA Limited",
"comodoca.com": "COMODO CA Limited",
"comsign.co.il": "ComSign Ltd.",
"comsign.co.uk": "ComSign Ltd.",
"comsigneurope.com": "ComSign Ltd.",
"cybertrust.co.jp": "Cybertrust Japan Co., Ltd.",
"cybertrust.ne.jp": "Cybertrust Japan Co., Ltd.",
"d-trust.de": "D-Trust GmbH",
"d-trust.net": "D-Trust GmbH",
"darkmatter.ae": "DarkMatter LLC",
"desc.gov.ae": "UAE Government",
"dfn.de": "Deutsche Telekom Security GmbH",
"digicert.com": "DigiCert, Inc.",
"digicert.ne.jp": "DigiCert, Inc.",
"digitalcertvalidation.com": "DigiCert, Inc.",
"disig.sk": "Disig a.s.",
"docusign.fr": "OpenTrust",
"dtrust.de": "D-Trust GmbH",
"dvv.fi": "Digi- ja vaestotietovirasto CA",
"e-szigno.hu": "Microsec Ltd.",
"e-tugra.com": "E-Tugra EBG A.S.",
"e-tugra.com.tr": "E-Tugra EBG A.S.",
"eca.hinet.net": "Chunghwa Telecom Co., Ltd.",
"ecert.gov.hk": "Hongkong Post",
"edicomgroup.com": "EDICOM CAPITAL SL",
"elektronicznypodpis.pl": "Krajowa Izba Rozliczeniowa S.A.",
"emsign.com": "eMudhra Technologies Limited",
"entrust.net": "Entrust, Inc.",
"epki.com.tw": "Chunghwa Telecom Co., Ltd.",
"etugra.com": "E-Tugra EBG A.S.",
"etugra.com.tr": "E-Tugra EBG A.S.",
"fineid.fi": "Digi- ja vaestotietovirasto CA",
"firmaprofesional.com": "Firmaprofesional S.A.",
"fnmt.es": "FNMT-RCM",
"gca.nat.gov.tw": "行政院",
"gdca.com.cn": "Global Digital Cybersecurity Authority Co., Ltd.",
"geotrust.com": "GeoTrust Inc.",
"globalsign.com": "GlobalSign nv-sa",
"globaltrust.eu": "e-commerce monitoring GmbH",
"godaddy.com": "GoDaddy.com",
"gtlsca.nat.gov.tw": "行政院",
"harica.gr": "Hellenic Academic and Research Institutions CA",
"hightrusted.com": "WISeKey",
"hongkongpost.gov.hk": "Hongkong Post",
"identrust.com": "IdenTrust",
"imtrust.cn": "北京中科三方网络技术有限公司",
"intermediatecertificate.digitalcertvalidation.com": "DigiCert, Inc.",
"itrus.cn": "iTrusChina Co.,Ltd.",
"izenpe.com": "IZENPE S.A.",
"izenpe.eus": "IZENPE S.A.",
"jprs.jp": "Japan Registry Services Co., Ltd.",
"kamusm.gov.tr": "TUBITAK Kamu Sertifikasyon Merkezi",
"letsencrypt.org": "Let's Encrypt",
"microsoft.com": "Microsoft Corporation",
"msctrustgate.com": "MSC Trustgate.com Sdn. Bhd.",
"multicert.com": "MULTICERT - Serviços de Certificação Electrónica S.A.",
"navercloudtrust.com": "NAVER Cloud Trust Services Corp.",
"netlock.com": "NetLock Ltd.",
"netlock.eu": "NetLock Ltd.",
"netlock.hu": "NetLock Ltd.",
"netlock.net": "NetLock Ltd.",
"networksolutions.com": "Network Solutions L.L.C.",
"nrca.go.th": "National Telecom Public Company Limited",
"oaticerts.com": "Open Access Technology International Inc",
"oiste.org": "OISTE Foundation",
"pki.apple.com": "Apple Inc.",
"pki.dfn.de": "Deutsche Telekom Security GmbH",
"pki.eviden.com": "Eviden",
"pki.goog": "Google Trust Services",
"pki.hinet.net": "Chunghwa Telecom Co., Ltd.",
"pkiworks.com": "CommScope",
"postsignum.cz": "Česká pošta, s.p.",
"publicca.hinet.net": "Chunghwa Telecom Co., Ltd.",
"quovadisglobal.com": "DigiCert, Inc.",
"rapidssl.com": "DigiCert, Inc.",
"secomtrust.net": "SECOM Trust Systems CO.,LTD.",
"sectigo.com": "Sectigo Limited",
"sheca.com": "UniTrust",
"skidsolutions.eu": "AS Sertifitseerimiskeskus",
"solutissl.com": "Soluti",
"ssl.com": "SSL Corporation",
"ssl.gov.sa": "Baud Telecom Company",
"ssl.gpki.go.kr": "Ministry of the Interior and Safety",
"sslcomgroup.com": "SSLCom Group ltd.",
"starfieldtech.com": "Starfield Technologies, Inc.",
"startcomca.com": "StartCom CA",
"startssl.com": "StartCom Ltd.",
"stratossl.digitalcertvalidation.com": "DigiCert, Inc.",
"swisssign.com": "SwissSign AG",
"symantec.com": "Symantec Corporation",
"telesec.de": "Deutsche Telekom Security GmbH",
"telia.com": "Telia Company AB",
"telia.fi": "Telia Company AB",
"telia.se": "Telia Company AB",
"thawte.com": "DigiCert, Inc.",
"tls.hinet.net": "Chunghwa Telecom Co., Ltd.",
"trust-provider.com": "Sectigo Limited",
"trust.telia.com": "Telia Company AB",
"trustasia.com": "TrustAsia Technologies, Inc.",
"trustcor.ca": "TrustCor Systems S. de R.L.",
"trustfactory.net": "TrustFactory(Pty)Ltd",
"tuntrust.tn": "Agence Nationale de Certification Electronique",
"twca.com.tw": "TWCA",
"usertrust.com": "The USERTRUST Network",
"vincasign.net": "VINTEGRIS SL",
"visa.com": "VISA",
"volusion.digitalcertvalidation.com": "DigiCert, Inc.",
"web.com": "Network Solutions L.L.C.",
"wisekey.com": "WISeKey",
"wosign.com": "WoSign CA Limited",
"www.certinomis.com": "Certinomis",
"www.certinomis.fr": "Certinomis",
"www.digicert.com": "DigiCert, Inc.",
"www.identrust.com": "IdenTrust"
}

View file

@ -19,167 +19,20 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export const issuers: Record<string, Array<string>> = {
"Actalis S.p.A.": ["actalis.it"],
"Amazon Trust Services LLC": [
"amazon.com",
"amazontrust.com",
"awstrust.com",
"amazonaws.com",
"aws.amazon.com",
],
"ANF AC": ["anf.es"],
"Asseco Data Systems (formely Certum)": ["certum.pl", "certum.eu"],
Apple: ["pki.apple.com"],
Atos: ["atos.net"],
"Beijing CA": ["bjca.cn"],
"Buypass AS": ["buypass.com", "buypass.no"],
CAcert: ["cacert.org"],
"AC Camerfirma S.A.": ["camerfirma.com"],
CATCert: ["aoc.cat"],
"Fastly/Certainly": ["certainly.com"],
Certinomis: ["www.certinomis.com", "www.certinomis.fr"],
Dhimyotis: ["certigna.fr"],
Certizen: ["ecert.gov.hk", "hongkongpost.gov.hk"],
certSIGN: ["certsign.ro"],
"China Financial CA (CFCA)": ["cfca.com.cn"],
"China Internet Network Information Center (CNNIC)": ["cnnic.cn"],
"Chunghwa Telecom": [
"pki.hinet.net",
"tls.hinet.net",
"eca.hinet.net",
"epki.com.tw",
"publicca.hinet.net",
],
"ComSign Ltd": ["comsign.co.il", "comsign.co.uk", "comsigneurope.com"],
"Cybertrust Japan": ["cybertrust.ne.jp", "jcsinc.co.jp"],
"Deutsche Telekom Security": ["telesec.de"],
"DFN-PKI": ["pki.dfn.de", "dfn.de"],
DigiCert: [
"digicert.com",
"symantec.com",
"geotrust.com",
"rapidssl.com",
"thawte.com",
"digitalcertvalidation.com",
"volusion.digitalcertvalidation.com",
"stratossl.digitalcertvalidation.com",
"intermediatecertificate.digitalcertvalidation.com",
"1and1.digitalcertvalidation.com",
],
"DigitalSign CD": ["digitalsign.pt"],
"Disig, a.s.": ["disig.sk"],
DocuSign: ["docusign.fr"],
"D-Trust GmbH": ["dtrust.de", "d-trust.de", "dtrust.net", "d-trust.net"],
DigitalTrust: ["digitaltrust.ae"],
EDICOM: ["edicomgroup.com"],
"eMudhra Technologies Limited": ["emsign.com"],
Entrust: ["entrust.net", "affirmtrust.com"],
"E-TUGRA Inc.": ["e-tugra.com", "e-tugra.com.tr", "etugra.com", "etugra.com.tr"],
"AC Firmaprofesional CIF": ["firmaprofesional.com"],
"Guang Dong CA Co.": ["gdca.com.cn"],
GlobalSign: ["globalsign.com"],
"Global Trust": ["globaltrust.eu"],
"GoDaddy Inc.": ["godaddy.com", "starfieldtech.com"],
"Google Trust Services (GTS)": ["pki.goog", "google.com"],
"ACCV (Spain gov)": ["accv.es"],
"FNMT (Spain gov)": ["fnmt.es"],
"Agence Nationale de Certification Electronique (Tunisia gov)": ["tuntrust.tn"],
GRCA: ["gca.nat.gov.tw"],
HARICA: ["harica.gr"],
"Hongkong Post": ["ecert.gov.hk", "hongkongpost.gov.hk"],
IdenTrust: ["identrust.com", "www.identrust.com"],
"iTrusChina Co.": ["itrus.com.cn", "itrus.cn"],
"IZENPE S.A.": ["izenpe.com", "izenpe.eus"],
"Japan Registry Services": ["jprs.co.jp"],
"Kamu Sertifikasyon Merkezi": ["kamusm.gov.tr"],
"KPN Corporate Market BV": ["kpn.com"],
"Krajowa Izba Rozliczeniowa S.A. (KIR)": ["elektronicznypodpis.pl"],
LAWtrust: ["lawtrust.co.za"],
"Let's Encrypt": ["letsencrypt.org"],
"Logius PKIoverheid": ["logius.nl"],
"Microsec Ltd.": ["e-szigno.hu"],
Microsoft: ["microsoft.com"],
"Microsoft IT": ["ssladmin.microsoft.com"],
"MSC Trustgate": ["msctrustgate.com"],
"National Center for Digital Certification (NCDC)": ["ncdc.gov.sa"],
"NAVER Business Platform Corp.": ["certificate.naver.com"],
"NetLock Kft.": ["netlock.hu", "netlock.net", "netlock.eu"],
Networking4all: ["trustproviderbv.digitalcertvalidation.com"],
"Network Solutions LLC": ["networksolutions.com", "web.com"],
"OISTE Foundation": ["wisekey.com", "hightrusted.com", "certifyid.com", "oiste.org"],
"Open Access Technology International": ["oati.com"],
"Prvni certifikacni autorita, a.s.": ["ica.cz"],
PKIoverheid: ["www.pkioverheid.nl"],
QuoVadis: [
"quovadisglobal.com",
"digicert.com",
"digicert.ne.jp",
"cybertrust.ne.jp",
"symantec.com",
"thawte.com",
"geotrust.com",
"rapidssl.com",
"digitalcertvalidation.com",
],
"SECOM Trust Systems": ["secomtrust.net"],
Sectigo: ["sectigo.com", "comodo.com", "comodoca.com", "usertrust.com", "trust-provider.com"],
"Shanghai Electronic Certification Authority Co. Ltd": [
"sheca.com",
"imtrust.cn",
"wwwtrust.cn",
],
"SK ID Solutions AS": ["skidsolutions.eu"],
"SSL Corporation": ["ssl.com"],
"Skaitmeninio sertifikavimo centras (SSC)": ["ssc.lt"],
"SwissSign AG": [
"swisssign.com",
"swisssign.net",
"swissign.com",
"swisssign.ch",
"swisssign.li",
"swissign.li",
"swisssign.org",
"swisssign.biz",
"swisstsa.ch",
"swisstsa.li",
"digitalid.ch",
"digital-id.ch",
"zert.ch",
"rootsigning.com",
"root-signing.ch",
"ssl-certificate.ch",
"managed-pki.ch",
"managed-pki.de",
"swissstick.com",
"swisssigner.ch",
"pki-posta.ch",
"pki-poste.ch",
"pki-post.ch",
"trustdoc.ch",
"trustsign.ch",
"swisssigner.com",
"postsuisseid.ch",
"suisseid-service.ch",
"signdemo.com",
"sirb.com",
],
"SecureTrust Corporation": ["trustwave.com", "securetrust.com"],
"TAIWAN-CA Inc. (TWCA)": ["twca.com.tw"],
"Telia Finland Oyj": ["telia.com", "telia.fi", "telia.se"],
"TrustCor Systems": ["trustcor.ca"],
"T-Systems Enterprise Services": ["t-systems.com"],
Visa: ["visa.com"],
Zertificon: ["zertificon.com"],
"360": ["browser.360.cn"],
};
import data from "./caa-issuers.json";
export const rev_issuers: Record<string, string> = {};
export const rev_issuers: Record<string, string> = data;
for (const issuer in issuers) {
for (const dn of issuers[issuer]) {
rev_issuers[dn] = issuer;
}
export const issuers: Record<string, Array<string>> = {};
for (const dn in rev_issuers) {
const owner = rev_issuers[dn];
if (!issuers[owner]) issuers[owner] = [];
issuers[owner].push(dn);
}
for (const owner in issuers) {
issuers[owner].sort();
}
export default issuers;