From 24e62e18fa5702a12a33d2bc1214f9197ca0b1e1 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 5 Apr 2023 16:58:58 +0700 Subject: [PATCH 01/55] Use errors.Join instead of copied version --- errors.go | 43 ------------------------------------------- resolver.go | 2 +- 2 files changed, 1 insertion(+), 44 deletions(-) delete mode 100644 errors.go diff --git a/errors.go b/errors.go deleted file mode 100644 index 8b47c6c..0000000 --- a/errors.go +++ /dev/null @@ -1,43 +0,0 @@ -package ctrld - -// TODO(cuonglm): use stdlib once we bump minimum version to 1.20 - -func joinErrors(errs ...error) error { - n := 0 - for _, err := range errs { - if err != nil { - n++ - } - } - if n == 0 { - return nil - } - e := &joinError{ - errs: make([]error, 0, n), - } - for _, err := range errs { - if err != nil { - e.errs = append(e.errs, err) - } - } - return e -} - -type joinError struct { - errs []error -} - -func (e *joinError) Error() string { - var b []byte - for i, err := range e.errs { - if i > 0 { - b = append(b, '\n') - } - b = append(b, err.Error()...) - } - return string(b) -} - -func (e *joinError) Unwrap() []error { - return e.errs -} diff --git a/resolver.go b/resolver.go index 45537fa..77517e6 100644 --- a/resolver.go +++ b/resolver.go @@ -93,7 +93,7 @@ func (o *osResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, error errs = append(errs, res.err) } - return nil, joinErrors(errs...) + return nil, errors.Join(errs...) } func newDialer(dnsAddress string) *net.Dialer { From 0043fdf8599249d6a60546fbdf2558faef3f5c35 Mon Sep 17 00:00:00 2001 From: alexelisenko <39712468+alexelisenko@users.noreply.github.com> Date: Mon, 10 Apr 2023 19:20:54 -0400 Subject: [PATCH 02/55] enable compression --- .gitignore | 4 +++- cmd/ctrld/dns_proxy.go | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1c4efb6..4816731 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ - dist/ gon.hcl + +/Build +.DS_Store diff --git a/cmd/ctrld/dns_proxy.go b/cmd/ctrld/dns_proxy.go index 5143659..ee95cb4 100644 --- a/cmd/ctrld/dns_proxy.go +++ b/cmd/ctrld/dns_proxy.go @@ -236,6 +236,10 @@ func (p *prog) proxy(ctx context.Context, upstreams []string, failoverRcodes []i ctrld.Log(ctx, mainLog.Debug(), "failover rcode matched, process to next upstream") continue } + + // set compression, as it is not set by default when unpacking + answer.Compress = true + if p.cache != nil { ttl := ttlFromMsg(answer) now := time.Now() From 4b6a97674753079db3b0ace82817856c02e183ec Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 15 Mar 2023 23:25:44 +0700 Subject: [PATCH 03/55] all: initial support for setup linux router Wiring code to configure router when running ctrld. Future commits will add implementation for each supported platforms. --- cmd/ctrld/cli.go | 29 +++++++++--- cmd/ctrld/cli_router_linux.go | 70 +++++++++++++++++++++++++++ cmd/ctrld/cli_router_others.go | 5 ++ cmd/ctrld/main.go | 16 ++++--- internal/router/router.go | 86 ++++++++++++++++++++++++++++++++++ 5 files changed, 193 insertions(+), 13 deletions(-) create mode 100644 cmd/ctrld/cli_router_linux.go create mode 100644 cmd/ctrld/cli_router_others.go create mode 100644 internal/router/router.go diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index a2a8207..f510aa6 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -18,9 +18,8 @@ import ( "sync" "time" - "github.com/fsnotify/fsnotify" - "github.com/cuonglm/osinfo" + "github.com/fsnotify/fsnotify" "github.com/go-playground/validator/v10" "github.com/kardianos/service" "github.com/miekg/dns" @@ -33,6 +32,7 @@ import ( "github.com/Control-D-Inc/ctrld" "github.com/Control-D-Inc/ctrld/internal/controld" ctrldnet "github.com/Control-D-Inc/ctrld/internal/net" + "github.com/Control-D-Inc/ctrld/internal/router" ) const selfCheckFQDN = "verify.controld.com" @@ -200,6 +200,18 @@ func initCLI() { os.Exit(0) } + if runtime.GOOS == "linux" && onRouter { + mainLog.Debug().Msg("Router setup") + err := router.Configure(&cfg) + if errors.Is(err, router.ErrNotSupported) { + unsupportedPlatformHelp(cmd) + os.Exit(1) + } + if err != nil { + mainLog.Fatal().Err(err).Msg("failed to configure router") + } + } + close(waitCh) <-stopCh }, @@ -218,6 +230,8 @@ func initCLI() { _ = runCmd.Flags().MarkHidden("homedir") runCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`) _ = runCmd.Flags().MarkHidden("iface") + runCmd.Flags().BoolVarP(&onRouter, "router", "", false, `Configure onRouter for running ctrld`) + _ = runCmd.Flags().MarkHidden("router") rootCmd.AddCommand(runCmd) @@ -315,6 +329,8 @@ func initCLI() { startCmd.Flags().IntVarP(&cacheSize, "cache_size", "", 0, "Enable cache with size items") startCmd.Flags().StringVarP(&cdUID, "cd", "", "", "Control D resolver uid") startCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`) + startCmd.Flags().BoolVarP(&onRouter, "router", "", false, `Configure onRouter for running ctrld`) + _ = startCmd.Flags().MarkHidden("router") stopCmd := &cobra.Command{ PreRun: checkHasElevatedPrivilege, @@ -510,11 +526,6 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`, stopCmdAlias.Flags().StringVarP(&ifaceStartStop, "iface", "", "auto", `Reset DNS setting for iface, "auto" means the default interface gateway`) stopCmdAlias.Flags().AddFlagSet(stopCmd.Flags()) rootCmd.AddCommand(stopCmdAlias) - - if err := rootCmd.Execute(); err != nil { - stderrMsg(err.Error()) - os.Exit(1) - } } func writeConfigFile() error { @@ -802,3 +813,7 @@ func selfCheckStatus(status service.Status) service.Status { mainLog.Debug().Msgf("self-check against %q failed", selfCheckFQDN) return service.StatusUnknown } + +func unsupportedPlatformHelp(cmd *cobra.Command) { + cmd.PrintErrln("Unsupported or incorrectly chosen onRouter platform. Please open an issue and provide all relevant information: https://github.com/Control-D-Inc/ctrld/issues/new") +} diff --git a/cmd/ctrld/cli_router_linux.go b/cmd/ctrld/cli_router_linux.go new file mode 100644 index 0000000..b1dc4b4 --- /dev/null +++ b/cmd/ctrld/cli_router_linux.go @@ -0,0 +1,70 @@ +package main + +import ( + "log" + "os" + "os/exec" + "strings" + + "github.com/spf13/cobra" + + "github.com/Control-D-Inc/ctrld/internal/router" +) + +func initRouterCLI() { + validArgs := append(router.SupportedPlatforms(), "auto") + var b strings.Builder + b.WriteString("Auto-setup Control D on a onRouter.\n\nSupported platforms:\n\n") + for _, arg := range validArgs { + b.WriteString(" ₒ ") + b.WriteString(arg) + if arg == "auto" { + b.WriteString(" - detect the platform you are running on") + } + b.WriteString("\n") + } + + routerCmd := &cobra.Command{ + Use: "setup", + Short: b.String(), + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + _ = cmd.Help() + return + } + if len(args) != 1 { + _ = cmd.Help() + return + } + platform := args[0] + if platform == "auto" { + platform = router.Name() + } + switch platform { + case router.DDWrt, router.Merlin, router.OpenWrt, router.Ubios: + default: + unsupportedPlatformHelp(cmd) + } + exe, err := os.Executable() + if err != nil { + log.Fatal(err) + os.Exit(1) + } + + cmdArgs := []string{"start"} + cmdArgs = append(cmdArgs, os.Args[3:]...) + cmdArgs = append(cmdArgs, "--router=true") + command := exec.Command(exe, cmdArgs...) + command.Stdout = os.Stdout + command.Stderr = os.Stderr + command.Stdin = os.Stdin + if err := command.Run(); err != nil { + log.Fatal(err) + } + }, + } + tmpl := routerCmd.UsageTemplate() + tmpl = strings.Replace(tmpl, "{{.UseLine}}", "{{.UseLine}} [platform]", 1) + routerCmd.SetUsageTemplate(tmpl) + rootCmd.AddCommand(routerCmd) +} diff --git a/cmd/ctrld/cli_router_others.go b/cmd/ctrld/cli_router_others.go new file mode 100644 index 0000000..4a7b8c7 --- /dev/null +++ b/cmd/ctrld/cli_router_others.go @@ -0,0 +1,5 @@ +//go:build !linux + +package main + +func initRouterCLI() {} diff --git a/cmd/ctrld/main.go b/cmd/ctrld/main.go index 57ae02d..078c5db 100644 --- a/cmd/ctrld/main.go +++ b/cmd/ctrld/main.go @@ -26,18 +26,22 @@ var ( cacheSize int cfg ctrld.Config verbose int + cdUID string + iface string + ifaceStartStop string + onRouter bool - rootLogger = zerolog.New(io.Discard) - mainLog = rootLogger - - cdUID string - iface string - ifaceStartStop string + mainLog = zerolog.New(io.Discard) ) func main() { ctrld.InitConfig(v, "ctrld") initCLI() + initRouterCLI() + if err := rootCmd.Execute(); err != nil { + stderrMsg(err.Error()) + os.Exit(1) + } } func normalizeLogFilePath(logFilePath string) string { diff --git a/internal/router/router.go b/internal/router/router.go new file mode 100644 index 0000000..cb8b2b2 --- /dev/null +++ b/internal/router/router.go @@ -0,0 +1,86 @@ +package router + +import ( + "bytes" + "errors" + "fmt" + "os" + "os/exec" + "sync/atomic" + + "github.com/Control-D-Inc/ctrld" +) + +const ( + OpenWrt = "openwrt" + DDWrt = "ddwrt" + Merlin = "merlin" + Ubios = "ubios" +) + +// ErrNotSupported reports the current router is not supported error. +var ErrNotSupported = errors.New("unsupported platform") + +var routerAtomic atomic.Pointer[router] + +type router struct { + name string +} + +// SupportedPlatforms return all platforms that can be configured to run with ctrld. +func SupportedPlatforms() []string { + return []string{DDWrt, Merlin, OpenWrt, Ubios} +} + +// Configure change the given *ctrld.Config for running on the router. +func Configure(c *ctrld.Config) error { + name := Name() + switch name { + case DDWrt, Merlin, OpenWrt, Ubios: + default: + return ErrNotSupported + } + // TODO: implement all supported platforms. + fmt.Printf("Configuring router for: %s\n", name) + return nil +} + +// Name returns name of the router platform. +func Name() string { + if r := routerAtomic.Load(); r != nil { + return r.name + } + r := &router{} + r.name = distroName() + routerAtomic.Store(r) + return r.name +} + +func distroName() string { + switch { + case bytes.HasPrefix(uname(), []byte("DD-WRT")): + return DDWrt + case bytes.HasPrefix(uname(), []byte("ASUSWRT-Merlin")): + return Merlin + case haveFile("/etc/openwrt_version"): + return OpenWrt + case haveDir("/data/unifi"): + return Ubios + } + return "" +} + +func haveFile(file string) bool { + _, err := os.Stat(file) + return err == nil +} + +func haveDir(dir string) bool { + fi, _ := os.Stat(dir) + return fi != nil && fi.IsDir() +} + +func uname() []byte { + out, _ := exec.Command("uname", "-o").Output() + return out +} From c94be0df3579cb6b7455b344a7b985102fe196dd Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 22 Mar 2023 00:18:04 +0700 Subject: [PATCH 04/55] all: implement router setup for openwrt --- cmd/ctrld/cli.go | 32 ++++++++------- cmd/ctrld/cli_router_linux.go | 29 ++++++++++++-- cmd/ctrld/dns_proxy.go | 24 +++++++++--- cmd/ctrld/main.go | 1 - cmd/ctrld/prog.go | 1 + cmd/ctrld/service.go | 24 ++++++++++++ internal/router/openwrt.go | 74 +++++++++++++++++++++++++++++++++++ internal/router/procd.go | 24 ++++++++++++ internal/router/router.go | 57 ++++++++++++++++++++++++--- 9 files changed, 237 insertions(+), 29 deletions(-) create mode 100644 internal/router/openwrt.go create mode 100644 internal/router/procd.go diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index f510aa6..6b452bf 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -200,7 +200,7 @@ func initCLI() { os.Exit(0) } - if runtime.GOOS == "linux" && onRouter { + if router.Name() != "" { mainLog.Debug().Msg("Router setup") err := router.Configure(&cfg) if errors.Is(err, router.ErrNotSupported) { @@ -230,8 +230,6 @@ func initCLI() { _ = runCmd.Flags().MarkHidden("homedir") runCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`) _ = runCmd.Flags().MarkHidden("iface") - runCmd.Flags().BoolVarP(&onRouter, "router", "", false, `Configure onRouter for running ctrld`) - _ = runCmd.Flags().MarkHidden("router") rootCmd.AddCommand(runCmd) @@ -249,6 +247,7 @@ func initCLI() { } setDependencies(sc) sc.Arguments = append([]string{"run"}, osArgs...) + router.ConfigureService(sc) // No config path, generating config in HOME directory. noConfigStart := isNoConfigStart(cmd) @@ -276,12 +275,12 @@ func initCLI() { cfg.Service.LogPath = logPath processCDFlags() - // On Windows, the service will be run as SYSTEM, so if ctrld start as Admin, - // the user home dir is different, so pass specific arguments that relevant here. - if runtime.GOOS == "windows" { - if configPath == "" { - sc.Arguments = append(sc.Arguments, "--config="+defaultConfigFile) - } + + // Explicitly passing config, so on system where home directory could not be obtained, + // or sub-process env is different with the parent, we still behave correctly and use + // the expected config file. + if configPath == "" { + sc.Arguments = append(sc.Arguments, "--config="+defaultConfigFile) } prog := &prog{} @@ -297,7 +296,11 @@ func initCLI() { {s.Start, true}, } if doTasks(tasks) { - status, err := s.Status() + if err := router.PostInstall(); err != nil { + mainLog.Warn().Err(err).Msg("post installation failed, please check system/service log for details error") + return + } + status, err := serviceStatus(s) if err != nil { mainLog.Warn().Err(err).Msg("could not get service status") return @@ -329,8 +332,6 @@ func initCLI() { startCmd.Flags().IntVarP(&cacheSize, "cache_size", "", 0, "Enable cache with size items") startCmd.Flags().StringVarP(&cdUID, "cd", "", "", "Control D resolver uid") startCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`) - startCmd.Flags().BoolVarP(&onRouter, "router", "", false, `Configure onRouter for running ctrld`) - _ = startCmd.Flags().MarkHidden("router") stopCmd := &cobra.Command{ PreRun: checkHasElevatedPrivilege, @@ -381,7 +382,7 @@ func initCLI() { stderrMsg(err.Error()) return } - status, err := s.Status() + status, err := serviceStatus(s) if err != nil { stderrMsg(err.Error()) os.Exit(1) @@ -429,6 +430,9 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`, iface = "auto" } prog.resetDNS() + if err := router.Cleanup(); err != nil { + mainLog.Warn().Err(err).Msg("could not cleanup router") + } mainLog.Info().Msg("Service uninstalled") return } @@ -815,5 +819,5 @@ func selfCheckStatus(status service.Status) service.Status { } func unsupportedPlatformHelp(cmd *cobra.Command) { - cmd.PrintErrln("Unsupported or incorrectly chosen onRouter platform. Please open an issue and provide all relevant information: https://github.com/Control-D-Inc/ctrld/issues/new") + cmd.PrintErrln("Unsupported or incorrectly chosen router platform. Please open an issue and provide all relevant information: https://github.com/Control-D-Inc/ctrld/issues/new") } diff --git a/cmd/ctrld/cli_router_linux.go b/cmd/ctrld/cli_router_linux.go index b1dc4b4..c927010 100644 --- a/cmd/ctrld/cli_router_linux.go +++ b/cmd/ctrld/cli_router_linux.go @@ -14,7 +14,7 @@ import ( func initRouterCLI() { validArgs := append(router.SupportedPlatforms(), "auto") var b strings.Builder - b.WriteString("Auto-setup Control D on a onRouter.\n\nSupported platforms:\n\n") + b.WriteString("Auto-setup Control D on a router.\n\nSupported platforms:\n\n") for _, arg := range validArgs { b.WriteString(" ₒ ") b.WriteString(arg) @@ -52,8 +52,7 @@ func initRouterCLI() { } cmdArgs := []string{"start"} - cmdArgs = append(cmdArgs, os.Args[3:]...) - cmdArgs = append(cmdArgs, "--router=true") + cmdArgs = append(cmdArgs, osArgs(platform)...) command := exec.Command(exe, cmdArgs...) command.Stdout = os.Stdout command.Stderr = os.Stderr @@ -63,8 +62,32 @@ func initRouterCLI() { } }, } + // Keep these flags in sync with startCmd, except for "--router". + routerCmd.Flags().StringVarP(&configPath, "config", "c", "", "Path to config file") + routerCmd.Flags().StringVarP(&configBase64, "base64_config", "", "", "Base64 encoded config") + routerCmd.Flags().StringVarP(&listenAddress, "listen", "", "", "Listener address and port, in format: address:port") + routerCmd.Flags().StringVarP(&primaryUpstream, "primary_upstream", "", "", "Primary upstream endpoint") + routerCmd.Flags().StringVarP(&secondaryUpstream, "secondary_upstream", "", "", "Secondary upstream endpoint") + routerCmd.Flags().StringSliceVarP(&domains, "domains", "", nil, "List of domain to apply in a split DNS policy") + routerCmd.Flags().StringVarP(&logPath, "log", "", "", "Path to log file") + routerCmd.Flags().IntVarP(&cacheSize, "cache_size", "", 0, "Enable cache with size items") + routerCmd.Flags().StringVarP(&cdUID, "cd", "", "", "Control D resolver uid") + routerCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`) + tmpl := routerCmd.UsageTemplate() tmpl = strings.Replace(tmpl, "{{.UseLine}}", "{{.UseLine}} [platform]", 1) routerCmd.SetUsageTemplate(tmpl) rootCmd.AddCommand(routerCmd) } + +func osArgs(platform string) []string { + args := os.Args[2:] + n := 0 + for _, x := range args { + if x != platform && x != "auto" { + args[n] = x + n++ + } + } + return args[:n] +} diff --git a/cmd/ctrld/dns_proxy.go b/cmd/ctrld/dns_proxy.go index ee95cb4..0821813 100644 --- a/cmd/ctrld/dns_proxy.go +++ b/cmd/ctrld/dns_proxy.go @@ -17,10 +17,17 @@ import ( "github.com/Control-D-Inc/ctrld" "github.com/Control-D-Inc/ctrld/internal/dnscache" ctrldnet "github.com/Control-D-Inc/ctrld/internal/net" + "github.com/Control-D-Inc/ctrld/internal/router" ) const staleTTL = 60 * time.Second +var osUpstreamConfig = &ctrld.UpstreamConfig{ + Name: "OS resolver", + Type: ctrld.ResolverTypeOS, + Timeout: 2000, +} + func (p *prog) serveDNS(listenerNum string) error { listenerConfig := p.cfg.Listener[listenerNum] // make sure ip is allocated @@ -61,7 +68,7 @@ func (p *prog) serveDNS(listenerNum string) error { proto := proto // On Windows, there's no easy way for disabling/removing IPv6 DNS resolver, so we check whether we can // listen on ::1, then spawn a listener for receiving DNS requests. - if runtime.GOOS == "windows" && ctrldnet.SupportsIPv6ListenLocal() { + if needLocalIPv6Listener() { g.Go(func() error { s := &dns.Server{ Addr: net.JoinHostPort("::1", strconv.Itoa(listenerConfig.Port)), @@ -80,7 +87,7 @@ func (p *prog) serveDNS(listenerNum string) error { } g.Go(func() error { s := &dns.Server{ - Addr: net.JoinHostPort(listenerConfig.IP, strconv.Itoa(listenerConfig.Port)), + Addr: dnsListenAddress(listenerConfig), Net: proto, Handler: handler, } @@ -353,8 +360,13 @@ func ttlFromMsg(msg *dns.Msg) uint32 { return 0 } -var osUpstreamConfig = &ctrld.UpstreamConfig{ - Name: "OS resolver", - Type: ctrld.ResolverTypeOS, - Timeout: 2000, +func needLocalIPv6Listener() bool { + return ctrldnet.SupportsIPv6ListenLocal() && runtime.GOOS == "windows" +} + +func dnsListenAddress(lc *ctrld.ListenerConfig) string { + if addr := router.ListenAddress(); addr != "" { + return addr + } + return net.JoinHostPort(lc.IP, strconv.Itoa(lc.Port)) } diff --git a/cmd/ctrld/main.go b/cmd/ctrld/main.go index 078c5db..656e2e8 100644 --- a/cmd/ctrld/main.go +++ b/cmd/ctrld/main.go @@ -29,7 +29,6 @@ var ( cdUID string iface string ifaceStartStop string - onRouter bool mainLog = zerolog.New(io.Discard) ) diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index 0206402..5075630 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -25,6 +25,7 @@ var errWindowsAddrInUse = syscall.Errno(0x2740) var svcConfig = &service.Config{ Name: "ctrld", DisplayName: "Control-D Helper Service", + Option: service.KeyValue{}, } type prog struct { diff --git a/cmd/ctrld/service.go b/cmd/ctrld/service.go index 14834c6..0da893f 100644 --- a/cmd/ctrld/service.go +++ b/cmd/ctrld/service.go @@ -1,9 +1,12 @@ package main import ( + "bytes" "fmt" "os" + "os/exec" + "github.com/kardianos/service" "github.com/spf13/cobra" ) @@ -43,3 +46,24 @@ func checkHasElevatedPrivilege(cmd *cobra.Command, args []string) { os.Exit(1) } } + +func serviceStatus(s service.Service) (service.Status, error) { + status, err := s.Status() + if err != nil && service.Platform() == "unix-systemv" { + return unixSystemVServiceStatus() + } + return status, err +} + +func unixSystemVServiceStatus() (service.Status, error) { + out, err := exec.Command("/etc/init.d/ctrld", "status").CombinedOutput() + if err != nil { + return service.StatusUnknown, nil + } + switch string(bytes.TrimSpace(out)) { + case "running": + return service.StatusRunning, nil + default: + return service.StatusStopped, nil + } +} diff --git a/internal/router/openwrt.go b/internal/router/openwrt.go new file mode 100644 index 0000000..be1316b --- /dev/null +++ b/internal/router/openwrt.go @@ -0,0 +1,74 @@ +package router + +import ( + "bytes" + "errors" + "fmt" + "os" + "os/exec" + "strings" +) + +var errUCIEntryNotFound = errors.New("uci: Entry not found") + +const openwrtDNSMasqConfigPath = "/tmp/dnsmasq.d/ctrld.conf" +const openwrtDNSMasqConfigContent = `# GENERATED BY ctrld - DO NOT MODIFY +port=0 +` + +func setupOpenWrt() error { + // Delete dnsmasq port if set. + if _, err := uci("delete", "dhcp.@dnsmasq[0].port"); err != nil && !errors.Is(err, errUCIEntryNotFound) { + return err + } + // Disable dnsmasq as DNS server. + if err := os.WriteFile(openwrtDNSMasqConfigPath, []byte(openwrtDNSMasqConfigContent), 0600); err != nil { + return err + } + // Commit. + if _, err := uci("commit"); err != nil { + return err + } + // Restart dnsmasq service. + if err := restartDNSMasq(); err != nil { + return err + } + return nil +} + +func cleanupOpenWrt() error { + // Remove the custom dnsmasq config + if err := os.Remove(openwrtDNSMasqConfigPath); err != nil { + return err + } + // Restart dnsmasq service. + if err := restartDNSMasq(); err != nil { + return err + } + return nil +} + +func postInstallOpenWrt() error { + return exec.Command("/etc/init.d/ctrld", "enable").Run() +} + +func uci(args ...string) (string, error) { + cmd := exec.Command("uci", args...) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + if strings.HasPrefix(stderr.String(), errUCIEntryNotFound.Error()) { + return "", errUCIEntryNotFound + } + return "", fmt.Errorf("%s:%w", stderr.String(), err) + } + return strings.TrimSpace(stdout.String()), nil +} + +func restartDNSMasq() error { + if out, err := exec.Command("/etc/init.d/dnsmasq", "restart").CombinedOutput(); err != nil { + return fmt.Errorf("%s: %w", string(out), err) + } + return nil +} diff --git a/internal/router/procd.go b/internal/router/procd.go new file mode 100644 index 0000000..d363f39 --- /dev/null +++ b/internal/router/procd.go @@ -0,0 +1,24 @@ +package router + +const openWrtScript = `#!/bin/sh /etc/rc.common +USE_PROCD=1 +# After network starts +START=21 +# Before network stops +STOP=89 +cmd="{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}" +name="{{.Name}}" +pid_file="/var/run/${name}.pid" + +start_service() { + echo "Starting ${name}" + procd_open_instance + procd_set_param command ${cmd} + procd_set_param respawn # respawn automatically if something died + procd_set_param stdout 1 # forward stdout of the command to logd + procd_set_param stderr 1 # same for stderr + procd_set_param pidfile ${pid_file} # write a pid file on instance start and remove it on stop + procd_close_instance + echo "${name} has been started" +} +` diff --git a/internal/router/router.go b/internal/router/router.go index cb8b2b2..3fc5af5 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -8,6 +8,8 @@ import ( "os/exec" "sync/atomic" + "github.com/kardianos/service" + "github.com/Control-D-Inc/ctrld" ) @@ -21,7 +23,7 @@ const ( // ErrNotSupported reports the current router is not supported error. var ErrNotSupported = errors.New("unsupported platform") -var routerAtomic atomic.Pointer[router] +var routerPlatform atomic.Pointer[router] type router struct { name string @@ -32,11 +34,13 @@ func SupportedPlatforms() []string { return []string{DDWrt, Merlin, OpenWrt, Ubios} } -// Configure change the given *ctrld.Config for running on the router. +// Configure configures things for running ctrld on the router. func Configure(c *ctrld.Config) error { name := Name() switch name { - case DDWrt, Merlin, OpenWrt, Ubios: + case OpenWrt: + return setupOpenWrt() + case DDWrt, Merlin, Ubios: default: return ErrNotSupported } @@ -45,14 +49,57 @@ func Configure(c *ctrld.Config) error { return nil } +// ConfigureService performs necessary setup for running ctrld as a service on router. +func ConfigureService(sc *service.Config) { + name := Name() + switch name { + case OpenWrt: + sc.Option["SysvScript"] = openWrtScript + case DDWrt, Merlin, Ubios: + } +} + +// PostInstall performs task after installing ctrld on router. +func PostInstall() error { + name := Name() + switch name { + case OpenWrt: + return postInstallOpenWrt() + case DDWrt, Merlin, Ubios: + } + return nil +} + +// Cleanup cleans ctrld setup on the router. +func Cleanup() error { + name := Name() + switch name { + case OpenWrt: + return cleanupOpenWrt() + case DDWrt, Merlin, Ubios: + } + return nil +} + +// ListenAddress returns the listener address of ctrld on router. +func ListenAddress() string { + name := Name() + switch name { + case OpenWrt: + return ":53" + case DDWrt, Merlin, Ubios: + } + return "" +} + // Name returns name of the router platform. func Name() string { - if r := routerAtomic.Load(); r != nil { + if r := routerPlatform.Load(); r != nil { return r.name } r := &router{} r.name = distroName() - routerAtomic.Store(r) + routerPlatform.Store(r) return r.name } From 8a2cdbfaa374336dbd992b9c0e8b517a7c6bb32d Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 30 Mar 2023 01:41:30 +0700 Subject: [PATCH 05/55] all: implement router setup for ddwrt --- cmd/ctrld/cli.go | 65 +- cmd/ctrld/cli_router_linux.go | 1 + cmd/ctrld/main.go | 3 +- cmd/ctrld/prog.go | 10 + cmd/ctrld/service.go | 5 +- config.go | 20 +- config_quic.go | 1 + dot.go | 7 +- internal/certs/cacert.pem | 3372 ++++++++++++++++++++++++++++++ internal/certs/root_ca.go | 22 + internal/certs/root_ca_test.go | 27 + internal/controld/config.go | 7 + internal/router/ddwrt.go | 115 + internal/router/dnsmasq.go | 6 + internal/router/openwrt.go | 11 +- internal/router/router.go | 27 +- internal/router/service.go | 49 + internal/router/service_ddwrt.go | 289 +++ 18 files changed, 4001 insertions(+), 36 deletions(-) create mode 100644 internal/certs/cacert.pem create mode 100644 internal/certs/root_ca.go create mode 100644 internal/certs/root_ca_test.go create mode 100644 internal/router/ddwrt.go create mode 100644 internal/router/dnsmasq.go create mode 100644 internal/router/service.go create mode 100644 internal/router/service_ddwrt.go diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 6b452bf..8809f25 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -3,6 +3,7 @@ package main import ( "bytes" "context" + "crypto/x509" "encoding/base64" "errors" "fmt" @@ -30,6 +31,7 @@ import ( "tailscale.com/net/interfaces" "github.com/Control-D-Inc/ctrld" + "github.com/Control-D-Inc/ctrld/internal/certs" "github.com/Control-D-Inc/ctrld/internal/controld" ctrldnet "github.com/Control-D-Inc/ctrld/internal/net" "github.com/Control-D-Inc/ctrld/internal/router" @@ -46,6 +48,7 @@ var ( v = viper.NewWithOptions(viper.KeyDelimiter("::")) defaultConfigWritten = false defaultConfigFile = "ctrld.toml" + rootCertPool *x509.CertPool ) var basicModeFlags = []string{"listen", "primary_upstream", "secondary_upstream", "domains"} @@ -146,8 +149,13 @@ func initCLI() { {"config", false}, {"ctrld", writeDefaultConfig}, } + + dir, err := userHomeDir() + if err != nil { + log.Fatalf("failed to get config dir: %v", dir) + } for _, config := range configs { - ctrld.SetConfigName(v, config.name) + ctrld.SetConfigNameWithPath(v, config.name, dir) v.SetConfigFile(configPath) if readConfigFile(config.written) { break @@ -200,15 +208,21 @@ func initCLI() { os.Exit(0) } - if router.Name() != "" { - mainLog.Debug().Msg("Router setup") - err := router.Configure(&cfg) - if errors.Is(err, router.ErrNotSupported) { - unsupportedPlatformHelp(cmd) - os.Exit(1) - } - if err != nil { - mainLog.Fatal().Err(err).Msg("failed to configure router") + if setupRouter { + switch platform := router.Name(); { + case platform == router.DDWrt: + rootCertPool = certs.CACertPool() + fallthrough + case platform != "": + mainLog.Debug().Msg("Router setup") + err := router.Configure(&cfg) + if errors.Is(err, router.ErrNotSupported) { + unsupportedPlatformHelp(cmd) + os.Exit(1) + } + if err != nil { + mainLog.Fatal().Err(err).Msg("failed to configure router") + } } } @@ -230,6 +244,8 @@ func initCLI() { _ = runCmd.Flags().MarkHidden("homedir") runCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`) _ = runCmd.Flags().MarkHidden("iface") + runCmd.Flags().BoolVarP(&setupRouter, "router", "", false, `setup for running on router platforms`) + _ = runCmd.Flags().MarkHidden("router") rootCmd.AddCommand(runCmd) @@ -247,7 +263,9 @@ func initCLI() { } setDependencies(sc) sc.Arguments = append([]string{"run"}, osArgs...) - router.ConfigureService(sc) + if err := router.ConfigureService(sc); err != nil { + log.Fatal(err) + } // No config path, generating config in HOME directory. noConfigStart := isNoConfigStart(cmd) @@ -255,7 +273,7 @@ func initCLI() { if configPath != "" { v.SetConfigFile(configPath) } - if dir, err := os.UserHomeDir(); err == nil { + if dir, err := userHomeDir(); err == nil { setWorkingDirectory(sc, dir) if configPath == "" && writeDefaultConfig { defaultConfigFile = filepath.Join(dir, defaultConfigFile) @@ -332,6 +350,8 @@ func initCLI() { startCmd.Flags().IntVarP(&cacheSize, "cache_size", "", 0, "Enable cache with size items") startCmd.Flags().StringVarP(&cdUID, "cd", "", "", "Control D resolver uid") startCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`) + startCmd.Flags().BoolVarP(&setupRouter, "router", "", false, `setup for running on router platforms`) + _ = startCmd.Flags().MarkHidden("router") stopCmd := &cobra.Command{ PreRun: checkHasElevatedPrivilege, @@ -430,6 +450,7 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`, iface = "auto" } prog.resetDNS() + mainLog.Debug().Msg("Router cleanup") if err := router.Cleanup(); err != nil { mainLog.Warn().Err(err).Msg("could not cleanup router") } @@ -821,3 +842,23 @@ func selfCheckStatus(status service.Status) service.Status { func unsupportedPlatformHelp(cmd *cobra.Command) { cmd.PrintErrln("Unsupported or incorrectly chosen router platform. Please open an issue and provide all relevant information: https://github.com/Control-D-Inc/ctrld/issues/new") } + +func userHomeDir() (string, error) { + switch router.Name() { + case router.DDWrt, router.Merlin: + exe, err := os.Executable() + if err != nil { + return "", err + } + return filepath.Dir(exe), nil + } + // viper will expand for us. + if runtime.GOOS == "windows" { + return os.UserHomeDir() + } + dir := "/etc/controld" + if err := os.MkdirAll(dir, 0750); err != nil { + return "", err + } + return dir, nil +} diff --git a/cmd/ctrld/cli_router_linux.go b/cmd/ctrld/cli_router_linux.go index c927010..46dfd5f 100644 --- a/cmd/ctrld/cli_router_linux.go +++ b/cmd/ctrld/cli_router_linux.go @@ -53,6 +53,7 @@ func initRouterCLI() { cmdArgs := []string{"start"} cmdArgs = append(cmdArgs, osArgs(platform)...) + cmdArgs = append(cmdArgs, "--router") command := exec.Command(exe, cmdArgs...) command.Stdout = os.Stdout command.Stderr = os.Stderr diff --git a/cmd/ctrld/main.go b/cmd/ctrld/main.go index 656e2e8..79a5769 100644 --- a/cmd/ctrld/main.go +++ b/cmd/ctrld/main.go @@ -29,6 +29,7 @@ var ( cdUID string iface string ifaceStartStop string + setupRouter bool mainLog = zerolog.New(io.Discard) ) @@ -50,7 +51,7 @@ func normalizeLogFilePath(logFilePath string) string { if homedir != "" { return filepath.Join(homedir, logFilePath) } - dir, _ := os.UserHomeDir() + dir, _ := userHomeDir() if dir == "" { return logFilePath } diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index 5075630..1b7f25a 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -14,6 +14,7 @@ import ( "github.com/Control-D-Inc/ctrld" "github.com/Control-D-Inc/ctrld/internal/dnscache" + "github.com/Control-D-Inc/ctrld/internal/router" ) var logf = func(format string, args ...any) { @@ -77,6 +78,7 @@ func (p *prog) run() { } else { mainLog.Info().Str("bootstrap_ip", uc.BootstrapIP).Msgf("Using bootstrap IP for upstream.%s", n) } + uc.SetCertPool(rootCertPool) uc.SetupTransport() } @@ -165,6 +167,10 @@ func (p *prog) deAllocateIP() error { } func (p *prog) setDNS() { + // On router, ctrld run as a DNS provider, it does not have to change system DNS. + if router.Name() != "" { + return + } if cfg.Listener == nil || cfg.Listener["0"] == nil { return } @@ -193,6 +199,10 @@ func (p *prog) setDNS() { } func (p *prog) resetDNS() { + // See comment in p.setDNS method. + if router.Name() != "" { + return + } if iface == "" { return } diff --git a/cmd/ctrld/service.go b/cmd/ctrld/service.go index 0da893f..9d56ec5 100644 --- a/cmd/ctrld/service.go +++ b/cmd/ctrld/service.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "errors" "fmt" "os" "os/exec" @@ -24,12 +25,14 @@ type task struct { } func doTasks(tasks []task) bool { + var prevErr error for _, task := range tasks { if err := task.f(); err != nil { if task.abortOnError { - stderrMsg(err.Error()) + stderrMsg(errors.Join(prevErr, err).Error()) return false } + prevErr = err } } return true diff --git a/config.go b/config.go index fb97901..904eaf1 100644 --- a/config.go +++ b/config.go @@ -2,6 +2,8 @@ package ctrld import ( "context" + "crypto/tls" + "crypto/x509" "net" "net/http" "net/url" @@ -20,23 +22,26 @@ import ( ) // SetConfigName set the config name that ctrld will look for. +// DEPRECATED: use SetConfigNameWithPath instead. func SetConfigName(v *viper.Viper, name string) { - v.SetConfigName(name) - configPath := "$HOME" // viper has its own way to get user home directory: https://github.com/spf13/viper/blob/v1.14.0/util.go#L134 // To be consistent, we prefer os.UserHomeDir instead. if homeDir, err := os.UserHomeDir(); err == nil { configPath = homeDir } + SetConfigNameWithPath(v, name, configPath) +} + +// SetConfigNameWithPath set the config path and name that ctrld will look for. +func SetConfigNameWithPath(v *viper.Viper, name, configPath string) { + v.SetConfigName(name) v.AddConfigPath(configPath) v.AddConfigPath(".") } // InitConfig initializes default config values for given *viper.Viper instance. func InitConfig(v *viper.Viper, name string) { - SetConfigName(v, name) - v.SetDefault("listener", map[string]*ListenerConfig{ "0": { IP: "127.0.0.1", @@ -104,6 +109,7 @@ type UpstreamConfig struct { Timeout int `mapstructure:"timeout" toml:"timeout,omitempty" validate:"gte=0"` transport *http.Transport `mapstructure:"-" toml:"-"` http3RoundTripper http.RoundTripper `mapstructure:"-" toml:"-"` + certPool *x509.CertPool `mapstructure:"-" toml:"-"` g singleflight.Group bootstrapIPs []string @@ -152,6 +158,11 @@ func (uc *UpstreamConfig) Init() { } } +// SetCertPool sets the system cert pool used for TLS connections. +func (uc *UpstreamConfig) SetCertPool(cp *x509.CertPool) { + uc.certPool = cp +} + // SetupBootstrapIP manually find all available IPs of the upstream. // The first usable IP will be used as bootstrap IP of the upstream. func (uc *UpstreamConfig) SetupBootstrapIP() { @@ -297,6 +308,7 @@ func (uc *UpstreamConfig) setupDOHTransport() { func (uc *UpstreamConfig) setupDOHTransportWithoutPingUpstream() { uc.transport = http.DefaultTransport.(*http.Transport).Clone() uc.transport.IdleConnTimeout = 5 * time.Second + uc.transport.TLSClientConfig = &tls.Config{RootCAs: uc.certPool} dialerTimeoutMs := 2000 if uc.Timeout > 0 && uc.Timeout < dialerTimeoutMs { diff --git a/config_quic.go b/config_quic.go index 253fc4e..c9b641f 100644 --- a/config_quic.go +++ b/config_quic.go @@ -18,6 +18,7 @@ func (uc *UpstreamConfig) setupDOH3Transport() { func (uc *UpstreamConfig) setupDOH3TransportWithoutPingUpstream() { rt := &http3.RoundTripper{} + rt.TLSClientConfig = &tls.Config{RootCAs: uc.certPool} rt.Dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { host := addr ProxyLog.Debug().Msgf("debug dial context D0H3 %s - %s", addr, bootstrapDNS) diff --git a/dot.go b/dot.go index 4107467..11befb7 100644 --- a/dot.go +++ b/dot.go @@ -20,12 +20,13 @@ func (r *dotResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro // regardless of the machine DNS status. dialer := newDialer(net.JoinHostPort(bootstrapDNS, "53")) dnsClient := &dns.Client{ - Net: "tcp-tls", - Dialer: dialer, + Net: "tcp-tls", + Dialer: dialer, + TLSConfig: &tls.Config{RootCAs: r.uc.certPool}, } endpoint := r.uc.Endpoint if r.uc.BootstrapIP != "" { - dnsClient.TLSConfig = &tls.Config{ServerName: r.uc.Domain} + dnsClient.TLSConfig.ServerName = r.uc.Domain _, port, _ := net.SplitHostPort(endpoint) endpoint = net.JoinHostPort(r.uc.BootstrapIP, port) } diff --git a/internal/certs/cacert.pem b/internal/certs/cacert.pem new file mode 100644 index 0000000..2ae7b6c --- /dev/null +++ b/internal/certs/cacert.pem @@ -0,0 +1,3372 @@ +## +## Bundle of CA Root Certificates +## +## Certificate data from Mozilla as of: Tue Jan 10 04:12:06 2023 GMT +## +## This is a bundle of X.509 certificates of public Certificate Authorities +## (CA). These were automatically extracted from Mozilla's root certificates +## file (certdata.txt). This file can be found in the mozilla source tree: +## https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt +## +## It contains the certificates in PEM format and therefore +## can be directly used with curl / libcurl / php_curl, or with +## an Apache+mod_ssl webserver for SSL client authentication. +## Just configure this file as the SSLCACertificateFile. +## +## Conversion done with mk-ca-bundle.pl version 1.29. +## SHA256: 90c470e705b4b5f36f09684dc50e2b79c8b86989a848b62cd1a7bd6460ee65f6 +## + + +GlobalSign Root CA +================== +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx +GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds +b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD +VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa +DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc +THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb +Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP +c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX +gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF +AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj +Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG +j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH +hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC +X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- + +Entrust.net Premium 2048 Secure Server CA +========================================= +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u +ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp +bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV +BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx +NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3 +d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl +MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u +ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL +Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr +hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW +nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi +VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ +KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy +T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT +J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e +nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- + +Baltimore CyberTrust Root +========================= +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE +ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li +ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC +SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs +dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME +uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB +UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C +G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9 +XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr +l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI +VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB +BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh +cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5 +hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa +Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H +RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +Entrust Root Certification Authority +==================================== +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw +b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG +A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0 +MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu +MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu +Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v +dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz +A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww +Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68 +j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN +rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1 +MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH +hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM +Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa +v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS +W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0 +tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +Comodo AAA Services root +======================== +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw +MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl +c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG +C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs +i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW +Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH +Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK +Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f +BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl +cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz +LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm +7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z +8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C +12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +QuoVadis Root CA 2 +================== +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +QuoVadis Root CA 3 +================== +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +Security Communication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP +U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw +HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP +U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw +8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM +DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX +5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd +DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2 +JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g +0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a +mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ +s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ +6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi +FL39vmwLAw== +-----END CERTIFICATE----- + +XRamp Global CA Root +==================== +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE +BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj +dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx +HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg +U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu +IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx +foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE +zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs +AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry +xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap +oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC +AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc +/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n +nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz +8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +Go Daddy Class 2 CA +=================== +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY +VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG +A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g +RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD +ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv +2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32 +qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j +YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY +vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O +BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o +atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu +MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim +PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt +I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI +Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b +vZ8= +-----END CERTIFICATE----- + +Starfield Class 2 CA +==================== +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc +U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo +MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG +A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG +SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY +bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ +JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm +epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN +F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF +MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f +hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo +bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g +QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs +afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM +PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD +KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3 +QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +DigiCert Assured ID Root CA +=========================== +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx +MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO +9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy +UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW +/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy +oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf +GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF +66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq +hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc +EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn +SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i +8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +DigiCert Global Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw +MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn +TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5 +BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H +4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y +7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB +o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm +8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF +BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr +EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt +tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886 +UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +DigiCert High Assurance EV Root CA +================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw +KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw +MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ +MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu +Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t +Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS +OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 +MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ +NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe +h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY +JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ +V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp +myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK +mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K +-----END CERTIFICATE----- + +SwissSign Gold CA - G2 +====================== +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +SwissSign Silver CA - G2 +======================== +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +SecureTrust CA +============== +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy +dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe +BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX +OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t +DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH +GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b +01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH +ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj +aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ +KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu +SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf +mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ +nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +Secure Global CA +================ +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH +bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg +MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg +Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx +YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ +bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g +8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV +HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi +0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn +oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA +MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+ +OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn +CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5 +3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +COMODO Certification Authority +============================== +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb +MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD +T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH ++7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww +xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV +4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA +1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI +rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k +b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC +AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP +OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc +IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN ++8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ== +-----END CERTIFICATE----- + +COMODO ECC Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix +GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X +4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni +wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG +FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA +U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +Certigna +======== +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw +EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3 +MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI +Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q +XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH +GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p +ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg +DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf +Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ +tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ +BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J +SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA +hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+ +ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu +PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY +1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +ePKI Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx +MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq +MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs +IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi +lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv +qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX +12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O +WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+ +ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao +lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/ +vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi +Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi +MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0 +1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq +KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV +xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP +NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r +GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE +xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx +gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy +sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD +BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +certSIGN ROOT CA +================ +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD +VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa +Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE +CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I +JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH +rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2 +ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD +0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943 +AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B +Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB +AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8 +SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0 +x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt +vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz +TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +NetLock Arany (Class Gold) Főtanúsítvány +======================================== +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G +A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610 +dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB +cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx +MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO +ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6 +c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu +0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw +/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk +H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw +fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1 +neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW +qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta +YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna +NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu +dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +Hongkong Post Root CA 1 +======================= +-----BEGIN CERTIFICATE----- +MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoT +DUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMB4XDTAzMDUx +NTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25n +IFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1 +ApzQjVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEnPzlTCeqr +auh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjhZY4bXSNmO7ilMlHIhqqh +qZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9nnV0ttgCXjqQesBCNnLsak3c78QA3xMY +V18meMjWCnl3v/evt3a5pQuEF10Q6m/hq5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNV +HRMBAf8ECDAGAQH/AgEDMA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7i +h9legYsCmEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI37pio +l7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clBoiMBdDhViw+5Lmei +IAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJsEhTkYY2sEJCehFC78JZvRZ+K88ps +T/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpOfMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilT +c4afU9hDDl3WY4JxHYB0yvbiAmvZWg== +-----END CERTIFICATE----- + +SecureSign RootCA11 +=================== +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi +SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS +b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw +KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1 +cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL +TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO +wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq +g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP +O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA +bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX +t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh +OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r +bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ +Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01 +y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061 +lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I= +-----END CERTIFICATE----- + +Microsec e-Szigno Root CA 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER +MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv +c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE +BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt +U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA +fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG +0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA +pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm +1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC +AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf +QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE +FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o +lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX +I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02 +yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi +LXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +GlobalSign Root CA - R3 +======================= +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv +YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh +bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT +aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln +bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt +iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ +0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3 +rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl +OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2 +xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7 +lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8 +EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E +bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18 +YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r +kpeDMdmztcpHWD9f +-----END CERTIFICATE----- + +Autoridad de Certificacion Firmaprofesional CIF A62634068 +========================================================= +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA +BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 +MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw +QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB +NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD +Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P +B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY +7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH +ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI +plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX +MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX +LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK +bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU +vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1Ud +EwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH +DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp +cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA +bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx +ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx +51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk +R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP +T481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f +Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl +osS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR +crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR +saS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD +KCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi +6Et8Vcad+qMUu2WFbm5PEn4KPJ2V +-----END CERTIFICATE----- + +Izenpe.com +========== +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG +EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz +MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu +QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ +03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK +ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU ++zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC +PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT +OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK +F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK +0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+ +0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB +leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID +AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+ +SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG +NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l +Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga +kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q +hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs +g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5 +aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5 +nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC +ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo +Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z +WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- + +Go Daddy Root Certificate Authority - G2 +======================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu +MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G +A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq +9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD ++qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd +fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl +NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9 +BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac +vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r +5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV +N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1 +-----END CERTIFICATE----- + +Starfield Root Certificate Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0 +eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw +DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg +VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB +dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv +W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs +bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk +N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf +ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU +JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol +TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx +4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw +F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ +c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +Starfield Services Root Certificate Authority - G2 +================================================== +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl +IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT +dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2 +h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa +hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP +LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB +rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG +SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP +E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy +xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza +YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6 +-----END CERTIFICATE----- + +AffirmTrust Commercial +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw +MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb +DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV +C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6 +BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww +MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV +HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG +hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi +qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv +0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh +sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +AffirmTrust Networking +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw +MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE +Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI +dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24 +/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb +h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV +HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu +UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6 +12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23 +WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9 +/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +AffirmTrust Premium +=================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy +OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy +dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn +BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV +5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs ++7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd +GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R +p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI +S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04 +6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5 +/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo ++Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv +MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC +6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S +L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK ++4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV +BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg +IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60 +g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb +zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw== +-----END CERTIFICATE----- + +AffirmTrust Premium ECC +======================= +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV +BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx +MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U +cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ +N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW +BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK +BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X +57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM +eQ== +-----END CERTIFICATE----- + +Certum Trusted Network CA +========================= +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK +ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy +MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU +ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC +l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J +J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4 +fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0 +cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw +DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj +jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1 +mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj +Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +TWCA Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ +VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG +EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB +IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx +QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC +oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP +4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r +y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG +9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC +mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW +QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY +T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny +Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +Security Communication RootCA2 +============================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh +dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC +SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy +aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++ ++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R +3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV +spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K +EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8 +QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB +CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj +u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk +3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q +tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29 +mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +Actalis Authentication Root CA +============================== +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +Buypass Class 2 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X +DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1 +g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn +9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b +/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU +CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff +awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI +zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn +Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX +Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs +M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI +osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S +aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd +DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD +LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0 +oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC +wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS +CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN +rJgWVqA= +-----END CERTIFICATE----- + +Buypass Class 3 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X +DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH +sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR +5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh +7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ +ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH +2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV +/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ +RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA +Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq +j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G +uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG +Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8 +ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2 +KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz +6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug +UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe +eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi +Cp/HuZc= +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 3 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx +MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK +9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU +NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF +iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W +0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr +AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb +fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT +ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h +P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw== +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe +Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE +LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD +ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA +BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv +KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z +p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC +AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ +4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y +eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw +MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G +PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw +OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm +2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV +dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph +X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 EV 2009 +================================= +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS +egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh +zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T +7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60 +sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35 +11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv +cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v +ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El +MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp +b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh +c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+ +PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX +ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA +NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv +w9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +CA Disig Root R2 +================ +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw +EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp +ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx +EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp +c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC +w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia +xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7 +A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S +GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV +g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa +5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE +koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A +Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i +Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u +Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV +sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je +dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8 +1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx +mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01 +utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0 +sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg +UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV +7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +ACCVRAIZ1 +========= +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB +SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1 +MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH +UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM +jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0 +RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD +aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ +0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG +WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7 +8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR +5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J +9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK +Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw +Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu +Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM +Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA +QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh +AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA +YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj +AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA +IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk +aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0 +dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2 +MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI +hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E +R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN +YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49 +nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ +TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3 +sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg +Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd +3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p +EfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +TWCA Global Root CA +=================== +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT +CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD +QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK +EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C +nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV +r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR +Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV +tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W +KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99 +sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p +yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn +kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI +zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g +cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M +8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg +/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg +lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP +A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m +i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8 +EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3 +zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0= +-----END CERTIFICATE----- + +TeliaSonera Root CA v1 +====================== +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE +CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4 +MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW +VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+ +6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA +3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k +B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn +Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH +oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3 +F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ +oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7 +gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc +TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB +AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW +DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm +zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW +pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV +G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc +c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT +JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2 +qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6 +Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems +WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +E-Tugra Certification Authority +=============================== +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNVBAYTAlRSMQ8w +DQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamls +ZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN +ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMw +NTEyMDk0OFoXDTIzMDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmEx +QDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxl +cmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQD +DB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEA4vU/kwVRHoViVF56C/UYB4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vd +hQd2h8y/L5VMzH2nPbxHD5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5K +CKpbknSFQ9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEoq1+g +ElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3Dk14opz8n8Y4e0ypQ +BaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcHfC425lAcP9tDJMW/hkd5s3kc91r0 +E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsutdEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gz +rt48Ue7LE3wBf4QOXVGUnhMMti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAq +jqFGOjGY5RH8zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn +rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUXU8u3Zg5mTPj5 +dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6Jyr+zE7S6E5UMA8GA1UdEwEB +/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEG +MA0GCSqGSIb3DQEBCwUAA4ICAQAFNzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAK +kEh47U6YA5n+KGCRHTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jO +XKqYGwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c77NCR807 +VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3+GbHeJAAFS6LrVE1Uweo +a2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WKvJUawSg5TB9D0pH0clmKuVb8P7Sd2nCc +dlqMQ1DujjByTd//SffGqWfZbawCEeI6FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEV +KV0jq9BgoRJP3vQXzTLlyb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gT +Dx4JnW2PAJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpDy4Q0 +8ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8dNL/+I5c30jn6PQ0G +C7TbO6Orb1wdtn7os4I07QZcJA== +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 2 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx +MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ +SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F +vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970 +2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV +WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy +YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4 +r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf +vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR +3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg== +-----END CERTIFICATE----- + +Atos TrustedRoot 2011 +===================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU +cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4 +MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG +A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV +hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr +54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+ +DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320 +HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR +z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R +l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ +bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h +k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh +TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9 +61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G +3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +QuoVadis Root CA 1 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE +PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm +PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6 +Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN +ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l +g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV +7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX +9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f +iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg +t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI +hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3 +GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct +Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP ++V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh +3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa +wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6 +O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0 +FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV +hMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +QuoVadis Root CA 2 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh +ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY +NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t +oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o +MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l +V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo +L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ +sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD +6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh +lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI +hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K +pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9 +x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz +dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X +U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw +mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD +zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN +JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr +O3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +QuoVadis Root CA 3 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286 +IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL +Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe +6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3 +I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U +VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7 +5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi +Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM +dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt +rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI +hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS +t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ +TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du +DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib +Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD +hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX +0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW +dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2 +PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +DigiCert Assured ID Root G2 +=========================== +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw +MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH +35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq +bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw +VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP +YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn +lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO +w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv +0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz +d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW +hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M +jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +DigiCert Assured ID Root G3 +=========================== +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD +VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb +RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs +KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF +UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy +YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy +1vUhZscv6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +DigiCert Global Root G2 +======================= +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx +MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ +kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO +3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV +BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM +UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu +5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr +F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U +WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH +QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/ +iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +DigiCert Global Root G3 +======================= +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD +VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw +MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k +aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O +YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp +Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y +3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34 +VOKa5Vt8sycX +-----END CERTIFICATE----- + +DigiCert Trusted Root G4 +======================== +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw +HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp +pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o +k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa +vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY +QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6 +MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm +mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7 +f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH +dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8 +oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY +ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr +yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy +7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah +ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN +5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb +/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa +5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK +G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP +82Z+ +-----END CERTIFICATE----- + +COMODO RSA Certification Authority +================================== +-----BEGIN CERTIFICATE----- +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 +LaZRfyHBNVOFBkpdn627G190 +-----END CERTIFICATE----- + +USERTrust RSA Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +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 +Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +USERTrust ECC Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2 +0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez +nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV +HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB +HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu +9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R5 +=========================== +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6 +SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS +h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx +uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7 +yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +IdenTrust Commercial Root CA 1 +============================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS +b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES +MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB +IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld +hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/ +mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi +1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C +XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl +3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy +NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV +WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg +xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix +uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI +hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg +ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt +ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV +YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX +feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro +kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe +2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz +Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R +cGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +IdenTrust Public Sector Root CA 1 +================================= +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv +ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV +UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS +b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy +P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6 +Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI +rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf +qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS +mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn +ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh +LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v +iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL +4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B +Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw +DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A +mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt +GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt +m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx +NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4 +Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI +ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC +ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ +3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +Entrust Root Certification Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVy +bXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ug +b25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIw +HhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoT +DUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMx +OTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP +/vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXz +HHfV1IWNcCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKU +s/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4y +TGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRx +AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ6 +0B7vfec7aVHUbI2fkBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5Z +iXMRrEPR9RP/jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDgi +nWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P+ +vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xO +e4pIb4tF9g== +-----END CERTIFICATE----- + +Entrust Root Certification Authority - EC1 +========================================== +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn +YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYw +FAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2Fs +LXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQg +dXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt +IEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHy +AsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef +9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3h +vxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8 +kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +CFCA EV ROOT +============ +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE +CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB +IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw +MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD +DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV +BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD +7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN +uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW +ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7 +xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f +py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K +gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol +hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ +tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf +BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q +ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua +4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG +E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX +BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn +aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy +PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX +kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C +ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GB CA +=============================== +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQG +EwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl +ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAw +MzJaFw0zOTEyMDExNTEwMzFaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYD +VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds +b2JhbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3HEokKtaX +scriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGxWuR51jIjK+FTzJlFXHtP +rby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk +9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNku7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4o +Qnc/nSMbsrY9gBQHTC5P99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvg +GUpuuy9rM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI +hvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrghcViXfa43FK8+5/ea4n32cZiZBKpD +dHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0 +VQreUGdNZtGn//3ZwLWoo4rOZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEui +HZeeevJuQHHfaPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +SZAFIR ROOT CA2 +=============== +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQELBQAwUTELMAkG +A1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6ZW5pb3dhIFMuQS4xGDAWBgNV +BAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkwNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJ +BgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYD +VQQDDA9TWkFGSVIgUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5Q +qEvNQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT3PSQ1hNK +DJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw3gAeqDRHu5rr/gsUvTaE +2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr63fE9biCloBK0TXC5ztdyO4mTp4CEHCdJ +ckm1/zuVnsHMyAHs6A6KCpbns6aH5db5BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwi +ieDhZNRnvDF5YTy7ykHNXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P +AQH/BAQDAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsFAAOC +AQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw8PRBEew/R40/cof5 +O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOGnXkZ7/e7DDWQw4rtTw/1zBLZpD67 +oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCPoky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul +4+vJhaAlIDf7js4MNIThPIGyd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6 ++/NNIxuZMzSgLvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +Certum Trusted Network CA 2 +=========================== +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCBgDELMAkGA1UE +BhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1 +bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y +ayBDQSAyMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQ +TDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENB +IDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWADGSdhhuWZGc/IjoedQF9 +7/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+o +CgCXhVqqndwpyeI1B+twTUrWwbNWuKFBOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40b +Rr5HMNUuctHFY9rnY3lEfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2p +uTRZCr+ESv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1mo130 +GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02isx7QBlrd9pPPV3WZ +9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOWOZV7bIBaTxNyxtd9KXpEulKkKtVB +Rgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgezTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pye +hizKV/Ma5ciSixqClnrDvFASadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vM +BhBgu4M1t15n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI +hvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQF/xlhMcQSZDe28cmk4gmb3DW +Al45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTfCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuA +L55MYIR4PSFk1vtBHxgP58l1cb29XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMo +clm2q8KMZiYcdywmdjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tM +pkT/WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jbAoJnwTnb +w3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksqP/ujmv5zMnHCnsZy4Ypo +J/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Kob7a6bINDd82Kkhehnlt4Fj1F4jNy3eFm +ypnTycUm/Q1oBEauttmbjL4ZvrHG8hnjXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLX +is7VmFxWlgPF7ncGNf/P5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7 +zAYspsbiDrW5viSP +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions RootCA 2015 +======================================================= +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcT +BkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0 +aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAx +MTIxWjCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMg +QWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNV +BAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIw +MTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDC+Kk/G4n8PDwEXT2QNrCROnk8Zlrv +bTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+eh +iGsxr/CL0BgzuNtFajT0AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+ +6PAQZe104S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06CojXd +FPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV9Cz82XBST3i4vTwr +i5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrDgfgXy5I2XdGj2HUb4Ysn6npIQf1F +GQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2 +fu/Z8VFRfS0myGlZYeCsargqNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9mu +iNX6hME6wGkoLfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVdctA4GGqd83EkVAswDQYJKoZI +hvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0IXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+ +D1hYc2Ryx+hFjtyp8iY/xnmMsVMIM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrM +d/K4kPFox/la/vot9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+y +d+2VZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/eaj8GsGsVn +82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnhX9izjFk0WaSrT2y7Hxjb +davYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQl033DlZdwJVqwjbDG2jJ9SrcR5q+ss7F +Jej6A7na+RZukYT1HCjI/CbM1xyQVqdfbzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVt +J94Cj8rDtSvK6evIIVM4pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGa +JI7ZjnHKe7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0vm9q +p/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions ECC RootCA 2015 +=========================================================== +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0 +aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u +cyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgRUNDIFJvb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEw +MzcxMlowgaoxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmlj +IEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUQwQgYD +VQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIEVDQyBSb290 +Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKgQehLgoRc4vgxEZmGZE4JJS+dQS8KrjVP +dJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJajq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoK +Vlp8aQuqgAkkbH7BRqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFLQiC4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaeplSTA +GiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7SofTUwJCA3sS61kFyjn +dc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +ISRG Root X1 +============ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UE +BhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQD +EwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQG +EwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMT +DElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54r +Vygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj1 +3Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8K +b4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCN +Aymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ +4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf +1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu +hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQH +usEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/r +OPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4G +A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY +9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV +0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt +hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJw +TdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nx +e5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZA +JzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahD +YVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9n +JEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJ +m+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM +================ +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsxCzAJBgNVBAYT +AkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTAeFw0wODEw +MjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJD +TTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBALpxgHpMhm5/yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcf +qQgfBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAzWHFctPVr +btQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxFtBDXaEAUwED653cXeuYL +j2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z374jNUUeAlz+taibmSXaXvMiwzn15Cou +08YfxGyqxRxqAQVKL9LFwag0Jl1mpdICIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mw +WsXmo8RZZUc1g16p6DULmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnT +tOmlcYF7wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peSMKGJ +47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2ZSysV4999AeU14EC +ll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMetUqIJ5G+GR4of6ygnXYMgrwTJbFaa +i0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FPd9xf3E6Jobd2Sn9R2gzL+HYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1o +dHRwOi8vd3d3LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1RXxlDPiyN8+s +D8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYMLVN0V2Ue1bLdI4E7pWYjJ2cJ +j+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrT +Qfv6MooqtyuGC2mDOL7Nii4LcK2NJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW ++YJF1DngoABd15jmfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7 +Ixjp6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp1txyM/1d +8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B9kiABdcPUXmsEKvU7ANm +5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wokRqEIr9baRRmW1FMdW4R58MD3R++Lj8UG +rp1MYp3/RgT408m2ECVAdf4WqslKYIYvuu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +Amazon Root CA 1 +================ +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1 +MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgH +FzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQ +gLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0t +dHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7LWIbxcce +VOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3 +DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PM +CCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy +8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa +2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2 +xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +Amazon Root CA 2 +================ +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAyMB4XDTE1 +MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4 +kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg1dKmSYXp +N+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K8nu+NQWpEjTj82R0Yiw9 +AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvd +fLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAEx +kv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSS +btqDT6ZjmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0 +Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlN +c/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf+ +3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSw +DPBMMPQFWAJI/TPlUq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oA +A7CXDpO8Wqj2LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LE +YFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW +xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQ +gj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbW +aQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoV +Yh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE76KlXIx3 +KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H9jVlpNMKVv/1F2Rs76gi +JUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw= +-----END CERTIFICATE----- + +Amazon Root CA 3 +================ +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZB +f8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjr +Zt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSrttvXBp43 +rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkrBqWTrBqYaGFy+uGh0Psc +eGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteMYyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +Amazon Root CA 4 +================ +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN +/sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri +83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WBMAoGCCqGSM49BAMDA2gA +MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1 +AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 +============================================= +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIxGDAWBgNVBAcT +D0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxpbXNlbCB2ZSBUZWtub2xvamlr +IEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0wKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24g +TWVya2V6aSAtIEthbXUgU00xNjA0BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRp +ZmlrYXNpIC0gU3VydW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYD +VQQGEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXllIEJpbGlt +c2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklUQUsxLTArBgNVBAsTJEth +bXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBTTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11 +IFNNIFNTTCBLb2sgU2VydGlmaWthc2kgLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAr3UwM6q7a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y8 +6Ij5iySrLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INrN3wc +wv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2XYacQuFWQfw4tJzh0 +3+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/iSIzL+aFCr2lqBs23tPcLG07xxO9 +WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4fAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQU +ZT/HiobGPN08VFw1+DrtUgxHV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ +KoZIhvcNAQELBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPfIPP54+M638yc +lNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4lzwDGrpDxpa5RXI4s6ehlj2R +e37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0j +q5Rm+K37DwhuJi1/FwcJsoz7UMCflo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +GDCA TrustAUTH R5 ROOT +====================== +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCQ04xMjAw +BgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8wHQYDVQQD +DBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVow +YjELMAkGA1UEBhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJjDp6L3TQs +AlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBjTnnEt1u9ol2x8kECK62p +OqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+uKU49tm7srsHwJ5uu4/Ts765/94Y9cnrr +pftZTqfrlYwiOXnhLQiPzLyRuEH3FMEjqcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ +9Cy5WmYqsBebnh52nUpmMUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQ +xXABZG12ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloPzgsM +R6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3GkL30SgLdTMEZeS1SZ +D2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeCjGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4 +oR24qoAATILnsn8JuLwwoC8N9VKejveSswoAHQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx +9hoh49pwBiFYFIeFd3mqgnkCAwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlR +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZmDRd9FBUb1Ov9 +H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5COmSdI31R9KrO9b7eGZONn35 +6ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ryL3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd ++PwyvzeG5LuOmCd+uh8W4XAR8gPfJWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQ +HtZa37dG/OaG+svgIHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBD +F8Io2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV09tL7ECQ +8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQXR4EzzffHqhmsYzmIGrv +/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrqT8p+ck0LcIymSLumoRT2+1hEmRSuqguT +aaApJUqlyyvdimYHFngVV3Eb7PVHhPOeMTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== +-----END CERTIFICATE----- + +SSL.com Root Certification Authority RSA +======================================== +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxDjAM +BgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24x +MTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYw +MjEyMTczOTM5WhcNNDEwMjEyMTczOTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx +EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NM +LmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2RxFdHaxh3a3by/ZPkPQ/C +Fp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aXqhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8 +P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcCC52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/ge +oeOy3ZExqysdBP+lSgQ36YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkp +k8zruFvh/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrFYD3Z +fBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93EJNyAKoFBbZQ+yODJ +gUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVcUS4cK38acijnALXRdMbX5J+tB5O2 +UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi8 +1xtZPCvM8hnIk2snYxnP/Okm+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4s +bE6x/c+cCbqiM+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGVcpNxJK1ok1iOMq8bs3AD/CUr +dIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBcHadm47GUBwwyOabqG7B52B2ccETjit3E+ZUf +ijhDPwGFpUenPUayvOUiaPd7nNgsPgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAsl +u1OJD7OAUN5F7kR/q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjq +erQ0cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jra6x+3uxj +MxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90IH37hVZkLId6Tngr75qNJ +vTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/YK9f1JmzJBjSWFupwWRoyeXkLtoh/D1JI +Pb9s2KJELtFOt3JY04kTlf5Eq/jXixtunLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406y +wKBjYZC6VWg3dGq2ktufoYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NI +WuuA8ShYIc2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +SSL.com Root Certification Authority ECC +======================================== +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAv +BgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEy +MTgxNDAzWhcNNDEwMjEyMTgxNDAzWjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO +BgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI7Z4INcgn64mMU1jrYor+ +8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPgCemB+vNH06NjMGEwHQYDVR0OBBYEFILR +hXMw5zUE044CkvvlpNHEIejNMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTT +jgKS++Wk0cQh6M0wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCW +e+0F+S8Tkdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+gA0z +5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority RSA R2 +============================================== +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAlVTMQ4w +DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9u +MTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MB4XDTE3MDUzMTE4MTQzN1oXDTQyMDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI +DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYD +VQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvqM0fNTPl9fb69LT3w23jh +hqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssufOePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7w +cXHswxzpY6IXFJ3vG2fThVUCAtZJycxa4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTO +Zw+oz12WGQvE43LrrdF9HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+ +B6KjBSYRaZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcAb9Zh +CBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQGp8hLH94t2S42Oim +9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQVPWKchjgGAGYS5Fl2WlPAApiiECto +RHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMOpgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+Slm +JuwgUHfbSguPvuUCYHBBXtSuUDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48 ++qvWBkofZ6aYMBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa49QaAJadz20Zp +qJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBWs47LCp1Jjr+kxJG7ZhcFUZh1 +++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nx +Y/hoLVUE0fKNsKTPvDxeH3jnpaAgcLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2G +guDKBAdRUNf/ktUM79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDz +OFSz/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXtll9ldDz7 +CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEmKf7GUmG6sXP/wwyc5Wxq +lD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKKQbNmC1r7fSOl8hqw/96bg5Qu0T/fkreR +rwU7ZcegbLHNYhLDkBvjJc40vG93drEQw/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1 +hlMYegouCRw2n5H9gooiS9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX +9hwJ1C07mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority ECC +=========================================== +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xNDAy +BgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYw +MjEyMTgxNTIzWhcNNDEwMjEyMTgxNTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx +EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NM +LmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMAVIbc/R/fALhBYlzccBYy +3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1KthkuWnBaBu2+8KGwytAJKaNjMGEwHQYDVR0O +BBYEFFvKXuXe0oGqzagtZFG22XKbl+ZPMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe +5d7SgarNqC1kUbbZcpuX5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJ +N+vp1RPZytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZgh5Mm +m7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +GlobalSign Root CA - R6 +======================= +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEgMB4GA1UECxMX +R2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQxMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9i +YWxTaWduIFJvb3QgQ0EgLSBSNjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFs +U2lnbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQss +grRIxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1kZguSgMpE +3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxDaNc9PIrFsmbVkJq3MQbF +vuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJwLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqM +PKq0pPbzlUoSB239jLKJz9CgYXfIWHSw1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+ +azayOeSsJDa38O+2HBNXk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05O +WgtH8wY2SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/hbguy +CLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4nWUx2OVvq+aWh2IMP +0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpYrZxCRXluDocZXFSxZba/jJvcE+kN +b7gu3GduyYsRtYQUigAZcIN5kZeR1BonvzceMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQE +AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNV +HSMEGDAWgBSubAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGtIxg93eFyRJa0 +lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr6155wsTLxDKZmOMNOsIeDjHfrY +BzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLjvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFym +Fe944Hn+Xds+qkxV/ZoVqW/hpvvfcDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr +3TsTjxKM4kEaSHpzoHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB1 +0jZpnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfspA9MRf/T +uTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+vJJUEeKgDu+6B5dpffItK +oZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+t +JDfLRVpOoERIyNiwmcUVhAn21klJwGW45hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GC CA +=============================== +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQswCQYDVQQGEwJD +SDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEo +MCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRa +Fw00MjA1MDkwOTU4MzNaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQL +ExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4nieUqjFqdr +VCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4Wp2OQ0jnUsYd4XxiWD1Ab +NTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7TrYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0E +AwMDaAAwZQIwJsdpW9zV57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtk +AjEA2zQgMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- + +UCA Global G2 Root +================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBHbG9iYWwgRzIgUm9vdDAeFw0x +NjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlU +cnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxeYrb3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmT +oni9kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzmVHqUwCoV +8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/RVogvGjqNO7uCEeBHANBS +h6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDcC/Vkw85DvG1xudLeJ1uK6NjGruFZfc8o +LTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIjtm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/ +R+zvWr9LesGtOxdQXGLYD0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBe +KW4bHAyvj5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6DlNaBa +4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6iIis7nCs+dwp4wwc +OxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznPO6Q0ibd5Ei9Hxeepl2n8pndntd97 +8XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFIHEjMz15DD/pQwIX4wVZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo +5sOASD0Ee/ojL3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 +1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl1qnN3e92mI0A +Ds0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oUb3n09tDh05S60FdRvScFDcH9 +yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LVPtateJLbXDzz2K36uGt/xDYotgIVilQsnLAX +c47QN6MUPJiVAAwpBVueSUmxX8fjy88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHo +jhJi6IjMtX9Gl8CbEGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZk +bxqgDMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI+Vg7RE+x +ygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGyYiGqhkCyLmTTX8jjfhFn +RR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bXUB+K+wb1whnw0A== +-----END CERTIFICATE----- + +UCA Extended Validation Root +============================ +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9u +IFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMxMDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8G +A1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrs +iWogD4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvSsPGP2KxF +Rv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aopO2z6+I9tTcg1367r3CTu +eUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dksHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR +59mzLC52LqGj3n5qiAno8geK+LLNEOfic0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH +0mK1lTnj8/FtDw5lhIpjVMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KR +el7sFsLzKuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/TuDv +B0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41Gsx2VYVdWf6/wFlth +WG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs1+lvK9JKBZP8nm9rZ/+I8U6laUpS +NwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQDfwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS +3H5aBZ8eNJr34RQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL +BQADggIBADaNl8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR +ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQVBcZEhrxH9cM +aVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5c6sq1WnIeJEmMX3ixzDx/BR4 +dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb ++7lsq+KePRXBOy5nAliRn+/4Qh8st2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOW +F3sGPjLtx7dCvHaj2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwi +GpWOvpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2CxR9GUeOc +GMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmxcmtpzyKEC2IPrNkZAJSi +djzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbMfjKaiJUINlK73nZfdklJrX+9ZSCyycEr +dhh2n1ax +-----END CERTIFICATE----- + +Certigna Root CA +================ +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UE +BhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAwMiA0ODE0NjMwODEwMDAzNjEZ +MBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0xMzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjda +MFoxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYz +MDgxMDAwMzYxGTAXBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sOty3tRQgX +stmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9MCiBtnyN6tMbaLOQdLNyz +KNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPuI9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8 +JXrJhFwLrN1CTivngqIkicuQstDuI7pmTLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16 +XdG+RCYyKfHx9WzMfgIhC59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq +4NYKpkDfePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3YzIoej +wpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWTCo/1VTp2lc5ZmIoJ +lXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1kJWumIWmbat10TWuXekG9qxf5kBdI +jzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp/ +/TBt2dzhauH8XwIDAQABo4IBGjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczovL3d3d3cuY2Vy +dGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilodHRwOi8vY3JsLmNlcnRpZ25h +LmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYraHR0cDovL2NybC5kaGlteW90aXMuY29tL2Nl +cnRpZ25hcm9vdGNhLmNybDANBgkqhkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOIt +OoldaDgvUSILSo3L6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxP +TGRGHVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH60BGM+RFq +7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncBlA2c5uk5jR+mUYyZDDl3 +4bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdio2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd +8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS +6Cvu5zHbugRqh5jnxV/vfaci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaY +tlu3zM63Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayhjWZS +aX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw3kAP+HwV96LOPNde +E4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- + +emSign Root CA - G1 +=================== +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJJTjET +MBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl +ZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBHMTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgx +ODMwMDBaMGcxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVk +aHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQzf2N4aLTN +LnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO8oG0x5ZOrRkVUkr+PHB1 +cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aqd7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHW +DV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhMtTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ +6DqS0hdW5TUaQBw+jSztOd9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrH +hQIDAQABo0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQDAgEG +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31xPaOfG1vR2vjTnGs2 +vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjMwiI/aTvFthUvozXGaCocV685743Q +NcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6dGNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q ++Mri/Tm3R7nrft8EI6/6nAYH6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeih +U80Bv2noWgbyRQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- + +emSign ECC Root CA - G3 +======================= +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQGEwJJTjETMBEG +A1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg +MB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4 +MTgzMDAwWjBrMQswCQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11 +ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0WXTsuwYc +58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xySfvalY8L1X44uT6EYGQIr +MgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuBzhccLikenEhjQjAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+D +CBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7 +jHvrZQnD+JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- + +emSign Root CA - C1 +=================== +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkGA1UEBhMCVVMx +EzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNp +Z24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQD +ExNlbVNpZ24gUm9vdCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+up +ufGZBczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZHdPIWoU/ +Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH3DspVpNqs8FqOp099cGX +OFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvHGPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4V +I5b2P/AgNBbeCsbEBEV5f6f9vtKppa+cxSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleooms +lMuoaJuvimUnzYnu3Yy1aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+ +XJGFehiqTbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD +ggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87/kOXSTKZEhVb3xEp +/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4kqNPEjE2NuLe/gDEo2APJ62gsIq1 +NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrGYQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9 +wC68AivTxEDkigcxHpvOJpkT+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQ +BmIMMMAVSKeoWXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- + +emSign ECC Root CA - C3 +======================= +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQGEwJVUzETMBEG +A1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMxIDAeBgNVBAMTF2VtU2lnbiBF +Q0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQD +ExdlbVNpZ24gRUNDIFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd +6bciMK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4OjavtisIGJAnB9 +SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0OBBYEFPtaSNCAIEDyqOkA +B2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gA +MGUCMQC02C8Cif22TGK6Q04ThHK1rt0c3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwU +ZOR8loMRnLDRWmFLpg9J0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- + +Hongkong Post Root CA 3 +======================= +-----BEGIN CERTIFICATE----- +MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQELBQAwbzELMAkG +A1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJSG9uZyBLb25nMRYwFAYDVQQK +Ew1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2 +MDMwMjI5NDZaFw00MjA2MDMwMjI5NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtv +bmcxEjAQBgNVBAcTCUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMX +SG9uZ2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz +iNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFOdem1p+/l6TWZ5Mwc50tf +jTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mIVoBc+L0sPOFMV4i707mV78vH9toxdCim +5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOe +sL4jpNrcyCse2m5FHomY2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj +0mRiikKYvLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+TtbNe/ +JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZbx39ri1UbSsUgYT2u +y1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+l2oBlKN8W4UdKjk60FSh0Tlxnf0h ++bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YKTE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsG +xVd7GYYKecsAyVKvQv83j+GjHno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwID +AQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e +i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEwDQYJKoZIhvcN +AQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG7BJ8dNVI0lkUmcDrudHr9Egw +W62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCkMpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWld +y8joRTnU+kLBEUx3XZL7av9YROXrgZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov ++BS5gLNdTaqX4fnkGMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDc +eqFS3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJmOzj/2ZQw +9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+l6mc1X5VTMbeRRAc6uk7 +nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6cJfTzPV4e0hz5sy229zdcxsshTrD3mUcY +hcErulWuBurQB7Lcq9CClnXO0lD+mefPL5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB +60PZ2Pierc+xYw5F9KBaLJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fq +dBb9HxEGmpv0 +-----END CERTIFICATE----- + +Entrust Root Certification Authority - G4 +========================================= +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAwgb4xCzAJBgNV +BAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3Qu +bmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1 +dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eSAtIEc0MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYT +AlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eSAtIEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3D +umSXbcr3DbVZwbPLqGgZ2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV +3imz/f3ET+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j5pds +8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAMC1rlLAHGVK/XqsEQ +e9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73TDtTUXm6Hnmo9RR3RXRv06QqsYJn7 +ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNXwbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5X +xNMhIWNlUpEbsZmOeX7m640A2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV +7rtNOzK+mndmnqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 +dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwlN4y6mACXi0mW +Hv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNjc0kCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9n +MA0GCSqGSIb3DQEBCwUAA4ICAQAS5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4Q +jbRaZIxowLByQzTSGwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht +7LGrhFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/B7NTeLUK +YvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uIAeV8KEsD+UmDfLJ/fOPt +jqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbwH5Lk6rWS02FREAutp9lfx1/cH6NcjKF+ +m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKW +RGhXxNUzzxkvFMSUHHuk2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjA +JOgc47OlIQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk5F6G ++TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuYn/PIjhs4ViFqUZPT +kcpG2om3PVODLAgfi49T3f+sHw== +-----END CERTIFICATE----- + +Microsoft ECC Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgRUND +IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4 +MjMxNjA0WjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZRogPZnZH6 +thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYbhGBKia/teQ87zvH2RPUB +eMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM ++Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlf +Xu5gKcs68tvWMoQZP3zVL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaR +eNtUjGUBiudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- + +Microsoft RSA Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQg +UlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIw +NzE4MjMwMDIzWjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u +MTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZNt9GkMml +7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0ZdDMbRnMlfl7rEqUrQ7e +S0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw7 +1VdyvD/IybLeS2v4I2wDwAW9lcfNcztmgGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+ +dkC0zVJhUXAoP8XFWvLJjEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49F +yGcohJUcaDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaGYaRS +MLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6W6IYZVcSn2i51BVr +lMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4KUGsTuqwPN1q3ErWQgR5WrlcihtnJ +0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJ +ClTUFLkqqNfs+avNJVgyeY+QW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZCLgLNFgVZJ8og +6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OCgMNPOsduET/m4xaRhPtthH80 +dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk ++ONVFT24bcMKpBLBaYVu32TxU5nhSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex +/2kskZGT4d9Mozd2TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDy +AmH3pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGRxpl/j8nW +ZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiAppGWSZI1b7rCoucL5mxAyE +7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKT +c0QWbej09+CVgI+WXTik9KveCjCHk9hNAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D +5KbvtwEwXlGjefVwaaZBRA+GsCyRxj3qrg+E +-----END CERTIFICATE----- + +e-Szigno Root CA 2017 +===================== +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNVBAYTAkhVMREw +DwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUt +MjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJvb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZa +Fw00MjA4MjIxMjA3MDZaMHExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UE +CgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3pp +Z25vIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtvxie+RJCx +s1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+HWyx7xf58etqjYzBhMA8G +A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSHERUI0arBeAyxr87GyZDv +vzAEwDAfBgNVHSMEGDAWgBSHERUI0arBeAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEA +tVfd14pVCzbhhkT61NlojbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxO +svxyqltZ+efcMQ== +-----END CERTIFICATE----- + +certSIGN Root CA G2 +=================== +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlJPMRQw +EgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjAeFw0xNzAy +MDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lH +TiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAMDFdRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05 +N0IwvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZuIt4Imfk +abBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhpn+Sc8CnTXPnGFiWeI8Mg +wT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKscpc/I1mbySKEwQdPzH/iV8oScLumZfNp +dWO9lfsbl83kqK/20U6o2YpxJM02PbyWxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91Qqh +ngLjYl/rNUssuHLoPj1PrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732 +jcZZroiFDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fxDTvf +95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgyLcsUDFDYg2WD7rlc +z8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6CeWRgKRM+o/1Pcmqr4tTluCRVLERL +iohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud +DgQWBBSCIS1mxteg4BXrzkwJd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOB +ywaK8SJJ6ejqkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQlqiCA2ClV9+BB +/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0OJD7uNGzcgbJceaBxXntC6Z5 +8hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+cNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5 +BiKDUyUM/FHE5r7iOZULJK2v0ZXkltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklW +atKcsWMy5WHgUyIOpwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tU +Sxfj03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZkPuXaTH4M +NMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE1LlSVHJ7liXMvGnjSG4N +0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MXQRBdJ3NghVdJIgc= +-----END CERTIFICATE----- + +Trustwave Global Certification Authority +======================================== +-----BEGIN CERTIFICATE----- +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 +29FpHOTKyeC2nOnOcXHebD8WpHk= +-----END CERTIFICATE----- + +Trustwave Global ECC P256 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1 +NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABH77bOYj +43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoNFWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqm +P62jQzBBMA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt +0UrrdaVKEJmzsaGLSvcwCgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjz +RM4q3wghDDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 +-----END CERTIFICATE----- + +Trustwave Global ECC P384 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4 +NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGvaDXU1CDFH +Ba5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJj9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr +/TklZvFe/oyujUF5nQlgziip04pt89ZF1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNV +HQ8BAf8EBQMDBwYAMB0GA1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNn +ADBkAjA3AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsCMGcl +CrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVuSw== +-----END CERTIFICATE----- + +NAVER Global Root Certification Authority +========================================= +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEMBQAwaTELMAkG +A1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRGT1JNIENvcnAuMTIwMAYDVQQD +DClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4 +NDJaFw0zNzA4MTgyMzU5NTlaMGkxCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVT +UyBQTEFURk9STSBDb3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVAiQqrDZBb +UGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH38dq6SZeWYp34+hInDEW ++j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lEHoSTGEq0n+USZGnQJoViAbbJAh2+g1G7 +XNr4rRVqmfeSVPc0W+m/6imBEtRTkZazkVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2 +aacp+yPOiNgSnABIqKYPszuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4 +Yb8ObtoqvC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHfnZ3z +VHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaGYQ5fG8Ir4ozVu53B +A0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo0es+nPxdGoMuK8u180SdOqcXYZai +cdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3aCJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejy +YhbLgGvtPe31HzClrkvJE+2KAQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNV +HQ4EFgQU0p+I36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoNqo0hV4/GPnrK +21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatjcu3cvuzHV+YwIHHW1xDBE1UB +jCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bx +hYTeodoS76TiEJd6eN4MUZeoIUCLhr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTg +E34h5prCy8VCZLQelHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTH +D8z7p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8piKCk5XQ +A76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLRLBT/DShycpWbXgnbiUSY +qqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oG +I/hGoiLtk/bdmuYqh7GYVPEi92tF4+KOdh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmg +kpzNNIaRkPpkUZ3+/uul9XXeifdy +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM SERVIDORES SEGUROS +=================================== +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQswCQYDVQQGEwJF +UzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgwFgYDVQRhDA9WQVRFUy1RMjgy +NjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1SQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4 +MTIyMDA5MzczM1oXDTQzMTIyMDA5MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQt +UkNNMQ4wDAYDVQQLDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNB +QyBSQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LHsbI6GA60XYyzZl2hNPk2 +LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oKUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqG +SM49BAMDA2kAMGYCMQCuSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoD +zBOQn5ICMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJyv+c= +-----END CERTIFICATE----- + +GlobalSign Root R46 +=================== +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUAMEYxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJv +b3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAX +BgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08Es +CVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQGvGIFAha/ +r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud316HCkD7rRlr+/fKYIje +2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEt +bWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvj +K8Cd+RTyG/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD4 +12lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCNI/on +ccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzsx2sZy/N78CsHpdls +eVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9 +vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEM +BQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti2kM3S+LGteWy +gxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92 +CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZm +OUdkLG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qq +JZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwye +qiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboz +nxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7 +DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3 +QEUxeCp6 +-----END CERTIFICATE----- + +GlobalSign Root E46 +=================== +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYxCzAJBgNVBAYT +AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3Qg +RTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNV +BAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkB +jtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGddyXqBPCCj +QjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRL +gLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZk +vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+ +CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- + +GLOBALTRUST 2020 +================ +-----BEGIN CERTIFICATE----- +MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCQVQx +IzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVT +VCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYxMDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAh +BgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAy +MDIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWi +D59bRatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9ZYybNpyrO +VPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3QWPKzv9pj2gOlTblzLmM +CcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPwyJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCm +fecqQjuCgGOlYx8ZzHyyZqjC0203b+J+BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKA +A1GqtH6qRNdDYfOiaxaJSaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9OR +JitHHmkHr96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj04KlG +DfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9MedKZssCz3AwyIDMvU +clOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIwq7ejMZdnrY8XD2zHc+0klGvIg5rQ +mjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1Ud +IwQYMBaAFNwuH9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA +VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJCXtzoRlgHNQIw +4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd6IwPS3BD0IL/qMy/pJTAvoe9 +iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS +8cE54+X1+NZK3TTN+2/BT+MAi1bikvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2 +HcqtbepBEX4tdJP7wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxS +vTOBTI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6CMUO+1918 +oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn4rnvyOL2NSl6dPrFf4IF +YqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+IaFvowdlxfv1k7/9nR4hYJS8+hge9+6jl +gqispdNpQ80xiEmEU5LAsTkbOYMBMMTyqfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg== +-----END CERTIFICATE----- + +ANF Secure Server Root CA +========================= +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +Certum EC-384 CA +================ +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQswCQYDVQQGEwJQ +TDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2 +MDcyNDU0WhcNNDMwMzI2MDcyNDU0WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERh +dGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx +GTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATEKI6rGFtq +vm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7TmFy8as10CW4kjPMIRBSqn +iBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68KjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFI0GZnQkdjrzife81r1HfS+8EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNo +ADBlAjADVS2m5hjEfO/JUG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0 +QoSZ/6vnnvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- + +Certum Trusted Root CA +====================== +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +TunTrust Root CA +================ +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +HARICA TLS RSA Root CA 2021 +=========================== +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG +EwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u +cyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUz +OFoXDTQ1MDIxMzEwNTUzN1owbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRl +bWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNB +IFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569lmwVnlskN +JLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE4VGC/6zStGndLuwRo0Xu +a2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uva9of08WRiFukiZLRgeaMOVig1mlDqa2Y +Ulhu2wr7a89o+uOkXjpFc5gH6l8Cct4MpbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K +5FrZx40d/JiZ+yykgmvwKh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEv +dmn8kN3bLW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcYAuUR +0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqBAGMUuTNe3QvboEUH +GjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYqE613TBoYm5EPWNgGVMWX+Ko/IIqm +haZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHrW2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQ +CPxrvrNQKlr9qEgYRtaQQJKQCoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAUX15QvWiWkKQU +EapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3f5Z2EMVGpdAgS1D0NTsY9FVq +QRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxajaH6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxD +QpSbIPDRzbLrLFPCU3hKTwSUQZqPJzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcR +j88YxeMn/ibvBZ3PzzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5 +vZStjBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0/L5H9MG0 +qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pTBGIBnfHAT+7hOtSLIBD6 +Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79aPib8qXPMThcFarmlwDB31qlpzmq6YR/ +PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YWxw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnn +kf3/W9b3raYvAwtt41dU63ZTGI0RmLo= +-----END CERTIFICATE----- + +HARICA TLS ECC Root CA 2021 +=========================== +-----BEGIN CERTIFICATE----- +MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQswCQYDVQQGEwJH +UjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBD +QTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoX +DTQ1MDIxMzExMDEwOVowbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWlj +IGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJv +b3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7KKrxcm1l +AEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9YSTHMmE5gEYd103KUkE+b +ECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW +0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAi +rcJRQO9gcS3ujwLEXQNwSaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/Qw +CZ61IygNnxS2PFOiTAZpffpskcYqSUXm7LcT4Tps +-----END CERTIFICATE----- + +Autoridad de Certificacion Firmaprofesional CIF A62634068 +========================================================= +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCRVMxQjBA +BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 +MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIw +QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB +NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD +Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P +B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY +7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH +ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI +plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX +MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX +LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK +bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU +vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1Ud +DgQWBBRlzeurNR4APn7VdMActHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4w +gZswgZgGBFUdIAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j +b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABCAG8AbgBhAG4A +bwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAwADEANzAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9miWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL +4QjbEwj4KKE1soCzC1HA01aajTNFSa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDb +LIpgD7dvlAceHabJhfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1il +I45PVf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZEEAEeiGaP +cjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV1aUsIC+nmCjuRfzxuIgA +LI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2tCsvMo2ebKHTEm9caPARYpoKdrcd7b/+A +lun4jWq9GJAd/0kakFI3ky88Al2CdgtR5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH +9IBk9W6VULgRfhVwOEqwf9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpf +NIbnYrX9ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNKGbqE +ZycPvEJdvSRUDewdcAZfpLz6IHxV +-----END CERTIFICATE----- + +vTrus ECC Root CA +================= +-----BEGIN CERTIFICATE----- +MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMwRzELMAkGA1UE +BhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBS +b290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDczMTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAa +BgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+c +ToL0v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUde4BdS49n +TPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIwV53dVvHH4+m4SVBrm2nDb+zDfSXkV5UT +QJtS0zvzQBm8JsctBp61ezaf9SXUY2sAAjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQL +YgmRWAD5Tfs0aNoJrSEGGJTO +-----END CERTIFICATE----- + +vTrus Root CA +============= +-----BEGIN CERTIFICATE----- +MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQELBQAwQzELMAkG +A1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xFjAUBgNVBAMTDXZUcnVzIFJv +b3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMxMDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoG +A1UEChMTaVRydXNDaGluYSBDby4sTHRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZots +SKYcIrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykUAyyNJJrI +ZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+GrPSbcKvdmaVayqwlHeF +XgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z98Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KA +YPxMvDVTAWqXcoKv8R1w6Jz1717CbMdHflqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70 +kLJrxLT5ZOrpGgrIDajtJ8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2 +AXPKBlim0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZNpGvu +/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQUqqzApVg+QxMaPnu +1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHWOXSuTEGC2/KmSNGzm/MzqvOmwMVO +9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMBAAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYg +scasGrz2iTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOC +AgEAKbqSSaet8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd +nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1jbhd47F18iMjr +jld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvMKar5CKXiNxTKsbhm7xqC5PD4 +8acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIivTDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJn +xDHO2zTlJQNgJXtxmOTAGytfdELSS8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554Wg +icEFOwE30z9J4nfrI8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4 +sEb9b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNBUvupLnKW +nyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1PTi07NEPhmg4NpGaXutIc +SkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929vensBxXVsFy6K2ir40zSbofitzmdHxghm+H +l3s= +-----END CERTIFICATE----- + +ISRG Root X2 +============ +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQswCQYDVQQGEwJV +UzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElT +UkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVT +MSkwJwYDVQQKEyBJbnRlcm5ldCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNS +RyBSb290IFgyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0H +ttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9ItgKbppb +d9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZIzj0EAwMDaAAwZQIwe3lORlCEwkSHRhtF +cP9Ymd70/aTSVaYgLXTWNLxBo1BfASdWtL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5 +U6VR5CmD1/iQMVtCnwr1/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- + +HiPKI Root CA - G1 +================== +-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xGzAZBgNVBAMMEkhpUEtJ +IFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRaFw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYT +AlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kg +Um9vdCBDQSAtIEcxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0 +o9QwqNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twvVcg3Px+k +wJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6lZgRZq2XNdZ1AYDgr/SE +YYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnzQs7ZngyzsHeXZJzA9KMuH5UHsBffMNsA +GJZMoYFL3QRtU6M9/Aes1MU3guvklQgZKILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfd +hSi8MEyr48KxRURHH+CKFgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj +1jOXTyFjHluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDry+K4 +9a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ/W3c1pzAtH2lsN0/ +Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgMa/aOEmem8rJY5AIJEzypuxC00jBF +8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQD +AgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi +7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqcSE5XCV0vrPSl +tJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6FzaZsT0pPBWGTMpWmWSBUdGSquE +wx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9TcXzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07Q +JNBAsNB1CI69aO4I1258EHBGG3zgiLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv +5wiZqAxeJoBF1PhoL5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+Gpz +jLrFNe85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wrkkVbbiVg +hUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+vhV4nYWBSipX3tUZQ9rb +yltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQUYDksswBVLuT1sw5XxJFBAJw/6KXf6vb/ +yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R4 +=========================== +-----BEGIN CERTIFICATE----- +MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYDVQQLExtHbG9i +YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgwMTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9i +YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkW +ymOxuYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNVHQ8BAf8E +BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/+wpu+74zyTyjhNUwCgYI +KoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147bmF0774BxL4YSFlhgjICICadVGNA3jdg +UM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm +-----END CERTIFICATE----- + +GTS Root R1 +=========== +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV +UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg +UjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE +ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM +f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7raKb0 +xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnWr4+w +B7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXW +nOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk +9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zq +kUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92wO1A +K/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om3xPX +V2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDW +cfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQAD +ggIBAJ+qQibbC5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe +QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuyh6f88/qBVRRi +ClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM47HLwEXWdyzRSjeZ2axfG34ar +J45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8JZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYci +NuaCp+0KueIHoI17eko8cdLiA6EfMgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5me +LMFrUKTX5hgUvYU/Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJF +fbdT6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ0E6yove+ +7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm2tIMPNuzjsmhDYAPexZ3 +FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bbbP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3 +gm3c +-----END CERTIFICATE----- + +GTS Root R2 +=========== +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV +UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg +UjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE +ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv +CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY6Dlo7JUl +e3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAuMC6C/Pq8tBcKSOWIm8Wb +a96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS ++LFjKBC4swm4VndAoiaYecb+3yXuPuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7M +kogwTZq9TwtImoS1mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJG +r61K8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RWIr9q +S34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKaG73VululycslaVNV +J1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCqgc7dGtxRcw1PcOnlthYhGXmy5okL +dWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQAD +ggIBAB/Kzt3HvqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8 +0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyCB19m3H0Q/gxh +swWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2uNmSRXbBoGOqKYcl3qJfEycel +/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMgyALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVn +jWQye+mew4K6Ki3pHrTgSAai/GevHyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y5 +9PYjJbigapordwj6xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M +7YNRTOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924SgJPFI/2R8 +0L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV7LXTWtiBmelDGDfrs7vR +WGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjW +HYbL +-----END CERTIFICATE----- + +GTS Root R3 +=========== +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi +MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMw +HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ +R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjO +PQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout +736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24CejQjBA +MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP0/Eq +Er24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azT +L818+FsuVbu/3ZL3pAzcMeGiAjEA/JdmZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV +11RZt+cRLInUue4X +-----END CERTIFICATE----- + +GTS Root R4 +=========== +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi +MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQw +HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ +R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjO +PQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu +hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqjQjBA +MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV2Py1 +PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/C +r8deVl5c1RxYIigL9zC2L7F8AjEA8GE8p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh +4rsUecrNIdSUtUlD +-----END CERTIFICATE----- + +Telia Root CA v2 +================ +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQxCzAJBgNVBAYT +AkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2 +MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQK +DBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ7 +6zBqAMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9vVYiQJ3q +9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9lRdU2HhE8Qx3FZLgmEKn +pNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTODn3WhUidhOPFZPY5Q4L15POdslv5e2QJl +tI5c0BE0312/UqeBAMN/mUWZFdUXyApT7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW +5olWK8jjfN7j/4nlNW4o6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNr +RBH0pUPCTEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6WT0E +BXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63RDolUK5X6wK0dmBR4 +M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZIpEYslOqodmJHixBTB0hXbOKSTbau +BcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGjYzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7W +xy+G2CQ5MB0GA1UdDgQWBBRyrOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ +8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi0f6X+J8wfBj5 +tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMMA8iZGok1GTzTyVR8qPAs5m4H +eW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBSSRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+C +y748fdHif64W1lZYudogsYMVoe+KTTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygC +QMez2P2ccGrGKMOF6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15 +h2Er3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMtTy3EHD70 +sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pTVmBds9hCG1xLEooc6+t9 +xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAWysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQ +raVplI/owd8k+BsHMYeB2F326CjYSlKArBPuUBQemMc= +-----END CERTIFICATE----- + +D-TRUST BR Root CA 1 2020 +========================= +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE +RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0EgMSAy +MDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNV +BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7 +dPYSzuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0QVK5buXu +QqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/VbNafAkl1bK6CKBrqx9t +MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu +bmV0L2NybC9kLXRydXN0X2JyX3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP +PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD +AwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFWwKrY7RjEsK70Pvom +AjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHVdWNbFJWcHwHP2NVypw87 +-----END CERTIFICATE----- + +D-TRUST EV Root CA 1 2020 +========================= +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE +RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0EgMSAy +MDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNV +BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8 +ZRCC/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rDwpdhQntJ +raOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3OqQo5FD4pPfsazK2/umL +MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu +bmV0L2NybC9kLXRydXN0X2V2X3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP +PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD +AwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CAy/m0sRtW9XLS/BnR +AjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJbgfM0agPnIjhQW+0ZT0MW +-----END CERTIFICATE----- + +DigiCert TLS ECC P384 Root G5 +============================= +-----BEGIN CERTIFICATE----- +MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURpZ2lDZXJ0IFRMUyBFQ0MgUDM4 +NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMx +FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQg +Um9vdCBHNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1Tzvd +lHJS7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp0zVozptj +n4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICISB4CIfBFqMA4GA1UdDwEB +/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQCJao1H5+z8blUD2Wds +Jk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQLgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIx +AJSdYsiJvRmEFOml+wG4DXZDjC5Ty3zfDBeWUA== +-----END CERTIFICATE----- + +DigiCert TLS RSA4096 Root G5 +============================ +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBNMQswCQYDVQQG +EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0 +MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcNNDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2 +IFJvb3QgRzUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS8 +7IE+ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG02C+JFvuU +AT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgpwgscONyfMXdcvyej/Ces +tyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZMpG2T6T867jp8nVid9E6P/DsjyG244gXa +zOvswzH016cpVIDPRFtMbzCe88zdH5RDnU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnV +DdXifBBiqmvwPXbzP6PosMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9q +TXeXAaDxZre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cdLvvy +z6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvXKyY//SovcfXWJL5/ +MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNeXoVPzthwiHvOAbWWl9fNff2C+MIk +wcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPLtgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4E +FgQUUTMc7TZArxfTJc1paPKvTiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw +GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7HPNtQOa27PShN +lnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLFO4uJ+DQtpBflF+aZfTCIITfN +MBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQREtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/ +u4cnYiWB39yhL/btp/96j1EuMPikAdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9G +OUrYU9DzLjtxpdRv/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh +47a+p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilwMUc/dNAU +FvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WFqUITVuwhd4GTWgzqltlJ +yqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCKovfepEWFJqgejF0pW8hL2JpqA15w8oVP +bEtoL8pU9ozaMv7Da4M/OMZ+ +-----END CERTIFICATE----- + +Certainly Root R1 +================= +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAwPTELMAkGA1UE +BhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2VydGFpbmx5IFJvb3QgUjEwHhcN +MjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2Vy +dGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBANA21B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O +5MQTvqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbedaFySpvXl +8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b01C7jcvk2xusVtyWMOvwl +DbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGI +XsXwClTNSaa/ApzSRKft43jvRl5tcdF5cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkN +KPl6I7ENPT2a/Z2B7yyQwHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQ +AjeZjOVJ6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA2Cnb +rlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyHWyf5QBGenDPBt+U1 +VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMReiFPCyEQtkA6qyI6BJyLm4SGcprS +p6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBTgqj8ljZ9EXME66C6ud0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAsz +HQNTVfSVcOQrPbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d +8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi1wrykXprOQ4v +MMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrdrRT90+7iIgXr0PK3aBLXWopB +GsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9ditaY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+ +gjwN/KUD+nsa2UUeYNrEjvn8K8l7lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgH +JBu6haEaBQmAupVjyTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7 +fpYnKx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLyyCwzk5Iw +x06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5nwXARPbv0+Em34yaXOp/S +X3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6OV+KmalBWQewLK8= +-----END CERTIFICATE----- + +Certainly Root E1 +================= +-----BEGIN CERTIFICATE----- +MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQswCQYDVQQGEwJV +UzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBFMTAeFw0yMTA0 +MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlu +bHkxGjAYBgNVBAMTEUNlcnRhaW5seSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4 +fxzf7flHh4axpMCK+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9 +YBk2QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4hevIIgcwCgYIKoZIzj0E +AwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozmut6Dacpps6kFtZaSF4fC0urQe87YQVt8 +rgIwRt7qy12a7DLCZRawTDBcMPPaTnOGBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR +-----END CERTIFICATE----- + +E-Tugra Global Root CA RSA v3 +============================= +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +E-Tugra Global Root CA ECC v3 +============================= +-----BEGIN CERTIFICATE----- +MIICpTCCAiqgAwIBAgIUJkYZdzHhT28oNt45UYbm1JeIIsEwCgYIKoZIzj0EAwMwgYAxCzAJBgNV +BAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVncmEgRUJHIEEuUy4xHTAbBgNV +BAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYDVQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290IENB +IEVDQyB2MzAeFw0yMDAzMTgwOTQ2NThaFw00NTAzMTIwOTQ2NThaMIGAMQswCQYDVQQGEwJUUjEP +MA0GA1UEBxMGQW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1 +Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBFQ0MgdjMw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAASOmCm/xxAeJ9urA8woLNheSBkQKczLWYHMjLiSF4mDKpL2 +w6QdTGLVn9agRtwcvHbB40fQWxPa56WzZkjnIZpKT4YKfWzqTTKACrJ6CZtpS5iB4i7sAnCWH/31 +Rs7K3IKjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU/4Ixcj75xGZsrTie0bBRiKWQ +zPUwHQYDVR0OBBYEFP+CMXI++cRmbK04ntGwUYilkMz1MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjO +PQQDAwNpADBmAjEA5gVYaWHlLcoNy/EZCL3W/VGSGn5jVASQkZo1kTmZ+gepZpO6yGjUij/67W4W +Aie3AjEA3VoXK3YdZUKWpqxdinlW2Iob35reX8dQj7FbcQwm32pAAOwzkSFxvmjkI6TZraE3 +-----END CERTIFICATE----- + +Security Communication RootCA3 +============================== +-----BEGIN CERTIFICATE----- +MIIFfzCCA2egAwIBAgIJAOF8N0D9G/5nMA0GCSqGSIb3DQEBDAUAMF0xCzAJBgNVBAYTAkpQMSUw +IwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQDEx5TZWN1cml0eSBD +b21tdW5pY2F0aW9uIFJvb3RDQTMwHhcNMTYwNjE2MDYxNzE2WhcNMzgwMTE4MDYxNzE2WjBdMQsw +CQYDVQQGEwJKUDElMCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UE +AxMeU2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EzMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEA48lySfcw3gl8qUCBWNO0Ot26YQ+TUG5pPDXC7ltzkBtnTCHsXzW7OT4rCmDvu20r +hvtxosis5FaU+cmvsXLUIKx00rgVrVH+hXShuRD+BYD5UpOzQD11EKzAlrenfna84xtSGc4RHwsE +NPXY9Wk8d/Nk9A2qhd7gCVAEF5aEt8iKvE1y/By7z/MGTfmfZPd+pmaGNXHIEYBMwXFAWB6+oHP2 +/D5Q4eAvJj1+XCO1eXDe+uDRpdYMQXF79+qMHIjH7Iv10S9VlkZ8WjtYO/u62C21Jdp6Ts9EriGm +npjKIG58u4iFW/vAEGK78vknR+/RiTlDxN/e4UG/VHMgly1s2vPUB6PmudhvrvyMGS7TZ2crldtY +XLVqAvO4g160a75BflcJdURQVc1aEWEhCmHCqYj9E7wtiS/NYeCVvsq1e+F7NGcLH7YMx3weGVPK +p7FKFSBWFHA9K4IsD50VHUeAR/94mQ4xr28+j+2GaR57GIgUssL8gjMunEst+3A7caoreyYn8xrC +3PsXuKHqy6C0rtOUfnrQq8PsOC0RLoi/1D+tEjtCrI8Cbn3M0V9hvqG8OmpI6iZVIhZdXw3/JzOf +GAN0iltSIEdrRU0id4xVJ/CvHozJgyJUt5rQT9nO/NkuHJYosQLTA70lUhw0Zk8jq/R3gpYd0Vcw +CBEF/VfR2ccCAwEAAaNCMEAwHQYDVR0OBBYEFGQUfPxYchamCik0FW8qy7z8r6irMA4GA1UdDwEB +/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQDcAiMI4u8hOscNtybS +YpOnpSNyByCCYN8Y11StaSWSntkUz5m5UoHPrmyKO1o5yGwBQ8IibQLwYs1OY0PAFNr0Y/Dq9HHu +Tofjcan0yVflLl8cebsjqodEV+m9NU1Bu0soo5iyG9kLFwfl9+qd9XbXv8S2gVj/yP9kaWJ5rW4O +H3/uHWnlt3Jxs/6lATWUVCvAUm2PVcTJ0rjLyjQIUYWg9by0F1jqClx6vWPGOi//lkkZhOpn2ASx +YfQAW0q3nHE3GYV5v4GwxxMOdnE+OoAGrgYWp421wsTL/0ClXI2lyTrtcoHKXJg80jQDdwj98ClZ +XSEIx2C/pHF7uNkegr4Jr2VvKKu/S7XuPghHJ6APbw+LP6yVGPO5DtxnVW5inkYO0QR4ynKudtml ++LLfiAlhi+8kTtFZP1rUPcmTPCtk9YENFpb3ksP+MW/oKjJ0DvRMmEoYDjBU1cXrvMUVnuiZIesn +KwkK2/HmcBhWuwzkvvnoEKQTkrgc4NtnHVMDpCKn3F2SEDzq//wbEBrD2NCcnWXL0CsnMQMeNuE9 +dnUM/0Umud1RvCPHX9jYhxBAEg09ODfnRDwYwFMJZI//1ZqmfHAuc1Uh6N//g7kdPjIe1qZ9LPFm +6Vwdp6POXiUyK+OVrCoHzrQoeIY8LaadTdJ0MN1kURXbg4NR16/9M51NZg== +-----END CERTIFICATE----- + +Security Communication ECC RootCA1 +================================== +-----BEGIN CERTIFICATE----- +MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYTAkpQMSUwIwYD +VQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYDVQQDEyJTZWN1cml0eSBDb21t +dW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYxNjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTEL +MAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNV +BAMTIlNlY3VyaXR5IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+CnnfdldB9sELLo +5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpKULGjQjBAMB0GA1UdDgQW +BBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAK +BggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3L +snNdo4gIxwwCMQDAqy0Obe0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70e +N9k= +-----END CERTIFICATE----- diff --git a/internal/certs/root_ca.go b/internal/certs/root_ca.go new file mode 100644 index 0000000..294ee66 --- /dev/null +++ b/internal/certs/root_ca.go @@ -0,0 +1,22 @@ +package certs + +import ( + "crypto/x509" + _ "embed" + "sync" +) + +var ( + //go:embed cacert.pem + caRoots []byte + caCertPoolOnce sync.Once + caCertPool *x509.CertPool +) + +func CACertPool() *x509.CertPool { + caCertPoolOnce.Do(func() { + caCertPool = x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caRoots) + }) + return caCertPool +} diff --git a/internal/certs/root_ca_test.go b/internal/certs/root_ca_test.go new file mode 100644 index 0000000..fcfd16e --- /dev/null +++ b/internal/certs/root_ca_test.go @@ -0,0 +1,27 @@ +package certs + +import ( + "crypto/tls" + "net/http" + "testing" + "time" +) + +func TestCACertPool(t *testing.T) { + c := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: CACertPool(), + }, + }, + Timeout: 2 * time.Second, + } + resp, err := c.Get("https://freedns.controld.com/p1") + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + if !resp.TLS.HandshakeComplete { + t.Error("TLS handshake is not complete") + } +} diff --git a/internal/controld/config.go b/internal/controld/config.go index f323f99..7092d51 100644 --- a/internal/controld/config.go +++ b/internal/controld/config.go @@ -3,6 +3,7 @@ package controld import ( "bytes" "context" + "crypto/tls" "encoding/json" "fmt" "net" @@ -13,7 +14,9 @@ import ( "github.com/miekg/dns" "github.com/Control-D-Inc/ctrld" + "github.com/Control-D-Inc/ctrld/internal/certs" ctrldnet "github.com/Control-D-Inc/ctrld/internal/net" + "github.com/Control-D-Inc/ctrld/internal/router" ) const ( @@ -114,6 +117,10 @@ func FetchResolverConfig(uid string) (*ResolverConfig, error) { } return ctrldnet.Dialer.DialContext(ctx, proto, addr) } + + if router.Name() == router.DDWrt { + transport.TLSClientConfig = &tls.Config{RootCAs: certs.CACertPool()} + } client := http.Client{ Timeout: 10 * time.Second, Transport: transport, diff --git a/internal/router/ddwrt.go b/internal/router/ddwrt.go new file mode 100644 index 0000000..022e82e --- /dev/null +++ b/internal/router/ddwrt.go @@ -0,0 +1,115 @@ +package router + +import ( + "bytes" + "errors" + "fmt" + "os/exec" + "strings" +) + +const ( + nvramCtrldKeyPrefix = "ctrld_" + nvramCtrldSetupKey = "ctrld_setup" + nvramRCStartupKey = "rc_startup" +) + +var ddwrtJffs2NotEnabledErr = errors.New(`could not install service without jffs, follow this guide to enable: + +https://wiki.dd-wrt.com/wiki/index.php/Journalling_Flash_File_System +`) + +var nvramKeys = map[string]string{ + "dns_dnsmasq": "1", // Make dnsmasq running but disable DNS ability, ctrld will replace it. + "dnsmasq_options": dnsMasqConfigContent, // Configuration of dnsmasq set by ctrld. + "dns_crypt": "0", // Disable DNSCrypt. +} + +func setupDDWrt() error { + // Already setup. + if val, _ := nvram("get", nvramCtrldSetupKey); val == "1" { + return nil + } + + // Backup current value, store ctrld's configs. + for key, value := range nvramKeys { + old, err := nvram("get", key) + if err != nil { + return fmt.Errorf("%s: %w", old, err) + } + if out, err := nvram("set", nvramCtrldKeyPrefix+key+"="+old); err != nil { + return fmt.Errorf("%s: %w", out, err) + } + if out, err := nvram("set", key+"="+value); err != nil { + return fmt.Errorf("%s: %w", out, err) + } + } + + if out, err := nvram("set", nvramCtrldSetupKey+"=1"); err != nil { + return fmt.Errorf("%s: %w", out, err) + } + // Commit. + if out, err := nvram("commit"); err != nil { + return fmt.Errorf("%s: %w", out, err) + } + // Restart dnsmasq service. + if err := ddwrtRestartDNSMasq(); err != nil { + return err + } + return nil +} + +func cleanupDDWrt() error { + // Restore old configs. + for key := range nvramKeys { + ctrldKey := nvramCtrldKeyPrefix + key + old, err := nvram("get", ctrldKey) + if err != nil { + return fmt.Errorf("%s: %w", old, err) + } + _, _ = nvram("unset", ctrldKey) + if out, err := nvram("set", key+"="+old); err != nil { + return fmt.Errorf("%s: %w", out, err) + } + } + + if out, err := nvram("unset", "ctrld_setup"); err != nil { + return fmt.Errorf("%s: %w", out, err) + } + // Commit. + if out, err := nvram("commit"); err != nil { + return fmt.Errorf("%s: %w", out, err) + } + // Restart dnsmasq service. + if err := ddwrtRestartDNSMasq(); err != nil { + return err + } + return nil +} + +func postInstallDDWrt() error { + return nil +} + +func nvram(args ...string) (string, error) { + cmd := exec.Command("nvram", args...) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + return "", fmt.Errorf("%s:%w", stderr.String(), err) + } + return strings.TrimSpace(stdout.String()), nil +} + +func ddwrtRestartDNSMasq() error { + if out, err := exec.Command("restart_dns").CombinedOutput(); err != nil { + return fmt.Errorf("restart_dns: %s, %w", string(out), err) + } + return nil +} + +func ddwrtJff2Enabled() bool { + out, _ := nvram("get", "enable_jffs2") + return out == "1" +} diff --git a/internal/router/dnsmasq.go b/internal/router/dnsmasq.go new file mode 100644 index 0000000..35d9022 --- /dev/null +++ b/internal/router/dnsmasq.go @@ -0,0 +1,6 @@ +package router + +const dnsMasqConfigContent = `# GENERATED BY ctrld - DO NOT MODIFY +no-resolv +server=127.0.0.1#5353 +` diff --git a/internal/router/openwrt.go b/internal/router/openwrt.go index be1316b..177be36 100644 --- a/internal/router/openwrt.go +++ b/internal/router/openwrt.go @@ -12,9 +12,6 @@ import ( var errUCIEntryNotFound = errors.New("uci: Entry not found") const openwrtDNSMasqConfigPath = "/tmp/dnsmasq.d/ctrld.conf" -const openwrtDNSMasqConfigContent = `# GENERATED BY ctrld - DO NOT MODIFY -port=0 -` func setupOpenWrt() error { // Delete dnsmasq port if set. @@ -22,7 +19,7 @@ func setupOpenWrt() error { return err } // Disable dnsmasq as DNS server. - if err := os.WriteFile(openwrtDNSMasqConfigPath, []byte(openwrtDNSMasqConfigContent), 0600); err != nil { + if err := os.WriteFile(openwrtDNSMasqConfigPath, []byte(dnsMasqConfigContent), 0600); err != nil { return err } // Commit. @@ -30,7 +27,7 @@ func setupOpenWrt() error { return err } // Restart dnsmasq service. - if err := restartDNSMasq(); err != nil { + if err := openwrtRestartDNSMasq(); err != nil { return err } return nil @@ -42,7 +39,7 @@ func cleanupOpenWrt() error { return err } // Restart dnsmasq service. - if err := restartDNSMasq(); err != nil { + if err := openwrtRestartDNSMasq(); err != nil { return err } return nil @@ -66,7 +63,7 @@ func uci(args ...string) (string, error) { return strings.TrimSpace(stdout.String()), nil } -func restartDNSMasq() error { +func openwrtRestartDNSMasq() error { if out, err := exec.Command("/etc/init.d/dnsmasq", "restart").CombinedOutput(); err != nil { return fmt.Errorf("%s: %w", string(out), err) } diff --git a/internal/router/router.go b/internal/router/router.go index 3fc5af5..9987314 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -38,9 +38,11 @@ func SupportedPlatforms() []string { func Configure(c *ctrld.Config) error { name := Name() switch name { + case DDWrt: + return setupDDWrt() case OpenWrt: return setupOpenWrt() - case DDWrt, Merlin, Ubios: + case Merlin, Ubios: default: return ErrNotSupported } @@ -50,22 +52,29 @@ func Configure(c *ctrld.Config) error { } // ConfigureService performs necessary setup for running ctrld as a service on router. -func ConfigureService(sc *service.Config) { +func ConfigureService(sc *service.Config) error { name := Name() switch name { + case DDWrt: + if !ddwrtJff2Enabled() { + return ddwrtJffs2NotEnabledErr + } case OpenWrt: sc.Option["SysvScript"] = openWrtScript - case DDWrt, Merlin, Ubios: + case Merlin, Ubios: } + return nil } // PostInstall performs task after installing ctrld on router. func PostInstall() error { name := Name() switch name { + case DDWrt: + return postInstallDDWrt() case OpenWrt: return postInstallOpenWrt() - case DDWrt, Merlin, Ubios: + case Merlin, Ubios: } return nil } @@ -76,7 +85,9 @@ func Cleanup() error { switch name { case OpenWrt: return cleanupOpenWrt() - case DDWrt, Merlin, Ubios: + case DDWrt: + return cleanupDDWrt() + case Merlin, Ubios: } return nil } @@ -85,9 +96,9 @@ func Cleanup() error { func ListenAddress() string { name := Name() switch name { - case OpenWrt: - return ":53" - case DDWrt, Merlin, Ubios: + case DDWrt, OpenWrt: + return "127.0.0.1:5353" + case Merlin, Ubios: } return "" } diff --git a/internal/router/service.go b/internal/router/service.go new file mode 100644 index 0000000..8845564 --- /dev/null +++ b/internal/router/service.go @@ -0,0 +1,49 @@ +package router + +import ( + "os" + + "github.com/kardianos/service" +) + +func init() { + system := &linuxSystemService{ + name: "ddwrt", + detect: func() bool { return Name() == DDWrt }, + interactive: func() bool { + is, _ := isInteractive() + return is + }, + new: newddwrtService, + } + systems := append([]service.System{system}, service.AvailableSystems()...) + service.ChooseSystem(systems...) +} + +type linuxSystemService struct { + name string + detect func() bool + interactive func() bool + new func(i service.Interface, platform string, c *service.Config) (service.Service, error) +} + +func (sc linuxSystemService) String() string { + return sc.name +} +func (sc linuxSystemService) Detect() bool { + return sc.detect() +} +func (sc linuxSystemService) Interactive() bool { + return sc.interactive() +} +func (sc linuxSystemService) New(i service.Interface, c *service.Config) (service.Service, error) { + return sc.new(i, sc.String(), c) +} + +func isInteractive() (bool, error) { + ppid := os.Getppid() + if ppid == 1 { + return false, nil + } + return true, nil +} diff --git a/internal/router/service_ddwrt.go b/internal/router/service_ddwrt.go new file mode 100644 index 0000000..097b3e6 --- /dev/null +++ b/internal/router/service_ddwrt.go @@ -0,0 +1,289 @@ +package router + +import ( + "bytes" + "errors" + "fmt" + "os" + "os/exec" + "os/signal" + "strings" + "syscall" + "text/template" + + "github.com/kardianos/service" +) + +type ddwrtSvc struct { + i service.Interface + platform string + *service.Config + rcStartup string +} + +func newddwrtService(i service.Interface, platform string, c *service.Config) (service.Service, error) { + s := &ddwrtSvc{ + i: i, + platform: platform, + Config: c, + } + if err := os.MkdirAll("/jffs/etc/config", 0644); err != nil { + return nil, err + } + return s, nil +} + +func (s *ddwrtSvc) String() string { + if len(s.DisplayName) > 0 { + return s.DisplayName + } + return s.Name +} + +func (s *ddwrtSvc) Platform() string { + return s.platform +} + +func (s *ddwrtSvc) configPath() string { + return fmt.Sprintf("/jffs/etc/config/%s.startup", s.Config.Name) +} + +func (s *ddwrtSvc) template() *template.Template { + return template.Must(template.New("").Parse(ddwrtSvcScript)) +} + +func (s *ddwrtSvc) Install() error { + confPath := s.configPath() + if _, err := os.Stat(confPath); err == nil { + return fmt.Errorf("already installed: %s", confPath) + } + + path, err := os.Executable() + if err != nil { + return err + } + + if !strings.HasPrefix(path, "/jffs/") { + return errors.New("could not install service outside /jffs") + } + + var to = &struct { + *service.Config + Path string + }{ + s.Config, + path, + } + + f, err := os.Create(confPath) + if err != nil { + return err + } + defer f.Close() + + if err := s.template().Execute(f, to); err != nil { + return err + } + + if err = os.Chmod(confPath, 0755); err != nil { + return err + } + + var sb strings.Builder + if err := template.Must(template.New("").Parse(ddwrtStartupCmd)).Execute(&sb, to); err != nil { + return err + } + s.rcStartup = sb.String() + curVal, err := nvram("get", nvramRCStartupKey) + if err != nil { + return err + } + if _, err := nvram("set", nvramCtrldKeyPrefix+nvramRCStartupKey+"="+curVal); err != nil { + return err + } + val := strings.Join([]string{curVal, s.rcStartup + " &", fmt.Sprintf(`echo $! > "/tmp/%s.pid"`, s.Config.Name)}, "\n") + + if _, err := nvram("set", nvramRCStartupKey+"="+val); err != nil { + return err + } + if out, err := nvram("commit"); err != nil { + return fmt.Errorf("%s: %w", out, err) + } + + return nil +} + +func (s *ddwrtSvc) Uninstall() error { + if err := os.Remove(s.configPath()); err != nil { + return err + } + + ctrldStartupKey := nvramCtrldKeyPrefix + nvramRCStartupKey + rcStartup, err := nvram("get", ctrldStartupKey) + if err != nil { + return err + } + _, _ = nvram("unset", ctrldStartupKey) + if _, err := nvram("set", nvramRCStartupKey+"="+rcStartup); err != nil { + return err + } + if out, err := nvram("commit"); err != nil { + return fmt.Errorf("%s: %w", out, err) + } + + return nil +} + +func (s *ddwrtSvc) Logger(errs chan<- error) (service.Logger, error) { + if service.Interactive() { + return service.ConsoleLogger, nil + } + return s.SystemLogger(errs) +} + +func (s *ddwrtSvc) SystemLogger(errs chan<- error) (service.Logger, error) { + // TODO(cuonglm): detect syslog enable and return proper logger? + // this at least works with default configuration. + if service.Interactive() { + return service.ConsoleLogger, nil + + } + return &noopLogger{}, nil +} + +func (s *ddwrtSvc) Run() (err error) { + err = s.i.Start(s) + if err != nil { + return err + } + + var sigChan = make(chan os.Signal, 3) + signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) + <-sigChan + + return s.i.Stop(s) +} + +func (s *ddwrtSvc) Status() (service.Status, error) { + if _, err := os.Stat(s.configPath()); os.IsNotExist(err) { + return service.StatusUnknown, service.ErrNotInstalled + } + out, err := exec.Command(s.configPath(), "status").CombinedOutput() + if err != nil { + return service.StatusUnknown, err + } + switch string(bytes.TrimSpace(out)) { + case "running": + return service.StatusRunning, nil + default: + return service.StatusStopped, nil + } +} + +func (s *ddwrtSvc) Start() error { + return exec.Command(s.configPath(), "start").Run() +} + +func (s *ddwrtSvc) Stop() error { + return exec.Command(s.configPath(), "stop").Run() +} + +func (s *ddwrtSvc) Restart() error { + err := s.Stop() + if err != nil { + return err + } + return s.Start() +} + +type noopLogger struct { +} + +func (c noopLogger) Error(v ...interface{}) error { + return nil +} +func (c noopLogger) Warning(v ...interface{}) error { + return nil +} +func (c noopLogger) Info(v ...interface{}) error { + return nil +} +func (c noopLogger) Errorf(format string, a ...interface{}) error { + return nil +} +func (c noopLogger) Warningf(format string, a ...interface{}) error { + return nil +} +func (c noopLogger) Infof(format string, a ...interface{}) error { + return nil +} + +const ddwrtStartupCmd = `{{.Path}}{{range .Arguments}} {{.}}{{end}}` +const ddwrtSvcScript = `#!/bin/sh + +name="{{.Name}}" +cmd="{{.Path}}{{range .Arguments}} {{.}}{{end}}" +pid_file="/tmp/$name.pid" + +get_pid() { + cat "$pid_file" +} + +is_running() { + [ -f "$pid_file" ] && ps | grep -q "^ *$(get_pid) " +} + +case "$1" in + start) + if is_running; then + echo "Already started" + else + echo "Starting $name" + $cmd & + echo $! > "$pid_file" + chmod 600 "$pid_file" + if ! is_running; then + echo "Failed to start $name" + exit 1 + fi + fi + ;; + stop) + if is_running; then + echo -n "Stopping $name..." + kill "$(get_pid)" + for _ in 1 2 3 4 5; do + if ! is_running; then + echo "stopped" + if [ -f "$pid_file" ]; then + rm "$pid_file" + fi + exit 0 + fi + printf "." + sleep 2 + done + echo "failed to stop $name" + exit 1 + fi + exit 1 + ;; + restart) + $0 stop + $0 start + ;; + status) + if is_running; then + echo "running" + else + echo "stopped" + exit 1 + fi + ;; + *) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 + ;; +esac +exit 0 +` From 2c7d95bba28e6a0047d7968f48cd14a32b387a32 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 12 Apr 2023 23:38:52 +0700 Subject: [PATCH 06/55] Support query param in upstream value --- config.go | 5 ++ config_internal_test.go | 139 ++++++++++++++++++++++++++++++++++++++++ config_test.go | 113 -------------------------------- doh.go | 14 ++-- 4 files changed, 154 insertions(+), 117 deletions(-) diff --git a/config.go b/config.go index 904eaf1..b30d8a4 100644 --- a/config.go +++ b/config.go @@ -110,6 +110,7 @@ type UpstreamConfig struct { transport *http.Transport `mapstructure:"-" toml:"-"` http3RoundTripper http.RoundTripper `mapstructure:"-" toml:"-"` certPool *x509.CertPool `mapstructure:"-" toml:"-"` + u *url.URL `mapstructure:"-" toml:"-"` g singleflight.Group bootstrapIPs []string @@ -142,6 +143,10 @@ type Rule map[string][]string func (uc *UpstreamConfig) Init() { if u, err := url.Parse(uc.Endpoint); err == nil { uc.Domain = u.Host + switch uc.Type { + case ResolverTypeDOH, ResolverTypeDOH3: + uc.u = u + } } if uc.Domain != "" { return diff --git a/config_internal_test.go b/config_internal_test.go index 0a457d3..a470cf8 100644 --- a/config_internal_test.go +++ b/config_internal_test.go @@ -1,7 +1,10 @@ package ctrld import ( + "net/url" "testing" + + "github.com/stretchr/testify/assert" ) func TestUpstreamConfig_SetupBootstrapIP(t *testing.T) { @@ -19,3 +22,139 @@ func TestUpstreamConfig_SetupBootstrapIP(t *testing.T) { } t.Log(uc) } + +func TestUpstreamConfig_Init(t *testing.T) { + u1, _ := url.Parse("https://example.com") + u2, _ := url.Parse("https://example.com?k=v") + tests := []struct { + name string + uc *UpstreamConfig + expected *UpstreamConfig + }{ + { + "doh+doh3", + &UpstreamConfig{ + Name: "doh", + Type: "doh", + Endpoint: "https://example.com", + BootstrapIP: "", + Domain: "", + Timeout: 0, + }, + &UpstreamConfig{ + Name: "doh", + Type: "doh", + Endpoint: "https://example.com", + BootstrapIP: "", + Domain: "example.com", + Timeout: 0, + u: u1, + }, + }, + { + "doh+doh3 with query param", + &UpstreamConfig{ + Name: "doh", + Type: "doh", + Endpoint: "https://example.com?k=v", + BootstrapIP: "", + Domain: "", + Timeout: 0, + }, + &UpstreamConfig{ + Name: "doh", + Type: "doh", + Endpoint: "https://example.com?k=v", + BootstrapIP: "", + Domain: "example.com", + Timeout: 0, + u: u2, + }, + }, + { + "dot+doq", + &UpstreamConfig{ + Name: "dot", + Type: "dot", + Endpoint: "freedns.controld.com:8853", + BootstrapIP: "", + Domain: "", + Timeout: 0, + }, + &UpstreamConfig{ + Name: "dot", + Type: "dot", + Endpoint: "freedns.controld.com:8853", + BootstrapIP: "", + Domain: "freedns.controld.com", + Timeout: 0, + }, + }, + { + "dot+doq without port", + &UpstreamConfig{ + Name: "dot", + Type: "dot", + Endpoint: "freedns.controld.com", + BootstrapIP: "", + Domain: "", + Timeout: 0, + }, + &UpstreamConfig{ + Name: "dot", + Type: "dot", + Endpoint: "freedns.controld.com:853", + BootstrapIP: "", + Domain: "freedns.controld.com", + Timeout: 0, + }, + }, + { + "legacy", + &UpstreamConfig{ + Name: "legacy", + Type: "legacy", + Endpoint: "1.2.3.4:53", + BootstrapIP: "", + Domain: "", + Timeout: 0, + }, + &UpstreamConfig{ + Name: "legacy", + Type: "legacy", + Endpoint: "1.2.3.4:53", + BootstrapIP: "1.2.3.4", + Domain: "1.2.3.4", + Timeout: 0, + }, + }, + { + "legacy without port", + &UpstreamConfig{ + Name: "legacy", + Type: "legacy", + Endpoint: "1.2.3.4", + BootstrapIP: "", + Domain: "", + Timeout: 0, + }, + &UpstreamConfig{ + Name: "legacy", + Type: "legacy", + Endpoint: "1.2.3.4:53", + BootstrapIP: "1.2.3.4", + Domain: "1.2.3.4", + Timeout: 0, + }, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + tc.uc.Init() + assert.Equal(t, tc.expected, tc.uc) + }) + } +} diff --git a/config_test.go b/config_test.go index f315c92..e2d75c0 100644 --- a/config_test.go +++ b/config_test.go @@ -165,116 +165,3 @@ func configWithInvalidRcodes(t *testing.T) *ctrld.Config { } return cfg } - -func TestUpstreamConfig_Init(t *testing.T) { - tests := []struct { - name string - uc *ctrld.UpstreamConfig - expected *ctrld.UpstreamConfig - }{ - { - "doh+doh3", - &ctrld.UpstreamConfig{ - Name: "doh", - Type: "doh", - Endpoint: "https://example.com", - BootstrapIP: "", - Domain: "", - Timeout: 0, - }, - &ctrld.UpstreamConfig{ - Name: "doh", - Type: "doh", - Endpoint: "https://example.com", - BootstrapIP: "", - Domain: "example.com", - Timeout: 0, - }, - }, - { - "dot+doq", - &ctrld.UpstreamConfig{ - Name: "dot", - Type: "dot", - Endpoint: "freedns.controld.com:8853", - BootstrapIP: "", - Domain: "", - Timeout: 0, - }, - &ctrld.UpstreamConfig{ - Name: "dot", - Type: "dot", - Endpoint: "freedns.controld.com:8853", - BootstrapIP: "", - Domain: "freedns.controld.com", - Timeout: 0, - }, - }, - { - "dot+doq without port", - &ctrld.UpstreamConfig{ - Name: "dot", - Type: "dot", - Endpoint: "freedns.controld.com", - BootstrapIP: "", - Domain: "", - Timeout: 0, - }, - &ctrld.UpstreamConfig{ - Name: "dot", - Type: "dot", - Endpoint: "freedns.controld.com:853", - BootstrapIP: "", - Domain: "freedns.controld.com", - Timeout: 0, - }, - }, - { - "legacy", - &ctrld.UpstreamConfig{ - Name: "legacy", - Type: "legacy", - Endpoint: "1.2.3.4:53", - BootstrapIP: "", - Domain: "", - Timeout: 0, - }, - &ctrld.UpstreamConfig{ - Name: "legacy", - Type: "legacy", - Endpoint: "1.2.3.4:53", - BootstrapIP: "1.2.3.4", - Domain: "1.2.3.4", - Timeout: 0, - }, - }, - { - "legacy without port", - &ctrld.UpstreamConfig{ - Name: "legacy", - Type: "legacy", - Endpoint: "1.2.3.4", - BootstrapIP: "", - Domain: "", - Timeout: 0, - }, - &ctrld.UpstreamConfig{ - Name: "legacy", - Type: "legacy", - Endpoint: "1.2.3.4:53", - BootstrapIP: "1.2.3.4", - Domain: "1.2.3.4", - Timeout: 0, - }, - }, - } - - for _, tc := range tests { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - tc.uc.Init() - assert.Equal(t, tc.expected, tc.uc) - }) - } -} diff --git a/doh.go b/doh.go index 433cf41..4fd4bd6 100644 --- a/doh.go +++ b/doh.go @@ -7,13 +7,14 @@ import ( "fmt" "io" "net/http" + "net/url" "github.com/miekg/dns" ) func newDohResolver(uc *UpstreamConfig) *dohResolver { r := &dohResolver{ - endpoint: uc.Endpoint, + endpoint: uc.u, isDoH3: uc.Type == ResolverTypeDOH3, transport: uc.transport, http3RoundTripper: uc.http3RoundTripper, @@ -22,7 +23,7 @@ func newDohResolver(uc *UpstreamConfig) *dohResolver { } type dohResolver struct { - endpoint string + endpoint *url.URL isDoH3 bool transport *http.Transport http3RoundTripper http.RoundTripper @@ -33,9 +34,14 @@ func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro if err != nil { return nil, err } + enc := base64.RawURLEncoding.EncodeToString(data) - url := fmt.Sprintf("%s?dns=%s", r.endpoint, enc) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + query := r.endpoint.Query() + query.Add("dns", enc) + + endpoint := *r.endpoint + endpoint.RawQuery = query.Encode() + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil) if err != nil { return nil, fmt.Errorf("could not create request: %w", err) } From a5443d5ca417d74b1415c4fea449fcde946eb1ea Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 12 Apr 2023 10:07:16 +0700 Subject: [PATCH 07/55] all: implement router setup for merlin --- cmd/ctrld/cli.go | 2 +- cmd/ctrld/prog.go | 11 +- internal/router/dnsmasq.go | 28 +++ internal/router/merlin.go | 76 ++++++++ internal/router/merlin_test.go | 38 ++++ internal/router/router.go | 21 +- internal/router/service.go | 27 ++- internal/router/service_ddwrt.go | 6 +- internal/router/service_merlin.go | 308 ++++++++++++++++++++++++++++++ internal/router/signal.go | 7 + internal/router/signal_windows.go | 5 + internal/router/syslog.go | 49 +++++ internal/router/syslog_windows.go | 7 + 13 files changed, 565 insertions(+), 20 deletions(-) create mode 100644 internal/router/merlin.go create mode 100644 internal/router/merlin_test.go create mode 100644 internal/router/service_merlin.go create mode 100644 internal/router/signal.go create mode 100644 internal/router/signal_windows.go create mode 100644 internal/router/syslog.go create mode 100644 internal/router/syslog_windows.go diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 8809f25..1c7cbd5 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -152,7 +152,7 @@ func initCLI() { dir, err := userHomeDir() if err != nil { - log.Fatalf("failed to get config dir: %v", dir) + log.Fatalf("failed to get config dir: %v", err) } for _, config := range configs { ctrld.SetConfigNameWithPath(v, config.name, dir) diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index 1b7f25a..7388983 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -167,8 +167,10 @@ func (p *prog) deAllocateIP() error { } func (p *prog) setDNS() { - // On router, ctrld run as a DNS provider, it does not have to change system DNS. - if router.Name() != "" { + switch router.Name() { + case router.DDWrt, router.OpenWrt, router.Ubios: + // On router, ctrld run as a DNS forwarder, it does not have to change system DNS. + // Except for Merlin, which has WAN DNS setup on boot for NTP. return } if cfg.Listener == nil || cfg.Listener["0"] == nil { @@ -199,8 +201,9 @@ func (p *prog) setDNS() { } func (p *prog) resetDNS() { - // See comment in p.setDNS method. - if router.Name() != "" { + switch router.Name() { + case router.DDWrt, router.OpenWrt, router.Ubios: + // See comment in p.setDNS method. return } if iface == "" { diff --git a/internal/router/dnsmasq.go b/internal/router/dnsmasq.go index 35d9022..9c6fd2b 100644 --- a/internal/router/dnsmasq.go +++ b/internal/router/dnsmasq.go @@ -4,3 +4,31 @@ const dnsMasqConfigContent = `# GENERATED BY ctrld - DO NOT MODIFY no-resolv server=127.0.0.1#5353 ` + +const merlinDNSMasqPostConfPath = "/jffs/scripts/dnsmasq.postconf" +const merlinDNSMasqPostConfMarker = `# GENERATED BY ctrld - EOF` + +const merlinDNSMasqPostConf = `# GENERATED BY ctrld - DO NOT MODIFY + +#!/bin/sh + +config_file="$1" +. /usr/sbin/helper.sh + +pid=$(cat /tmp/ctrld.pid 2>/dev/null) +if [ -n "$pid" ] && [ -f "/proc/${pid}/cmdline" ]; then + pc_delete "servers-file" "$config_file" # no WAN DNS settings + pc_append "no-resolv" "$config_file" # do not read /etc/resolv.conf + pc_append "server=127.0.0.1#5354" "$config_file" # use ctrld as upstream + + + # For John fork + pc_delete "resolv-file" "$config_file" # no WAN DNS settings + + # Change /etc/resolv.conf, which may be changed by WAN DNS setup + pc_delete "nameserver" /etc/resolv.conf + pc_append "nameserver 127.0.0.1" /etc/resolv.conf + + exit 0 +fi +` diff --git a/internal/router/merlin.go b/internal/router/merlin.go new file mode 100644 index 0000000..0048d17 --- /dev/null +++ b/internal/router/merlin.go @@ -0,0 +1,76 @@ +package router + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "strings" + "unicode" +) + +func setupMerlin() error { + buf, err := os.ReadFile(merlinDNSMasqPostConfPath) + // Already setup. + if bytes.Contains(buf, []byte(merlinDNSMasqPostConfMarker)) { + return nil + } + if err != nil && !os.IsNotExist(err) { + return err + } + + data := strings.Join([]string{ + merlinDNSMasqPostConf, + "\n", + merlinDNSMasqPostConfMarker, + "\n", + string(buf), + }, "\n") + // Write dnsmasq post conf file. + if err := os.WriteFile(merlinDNSMasqPostConfPath, []byte(data), 0750); err != nil { + return err + } + // Restart dnsmasq service. + if err := merlinRestartDNSMasq(); err != nil { + return err + } + return nil +} + +func cleanupMerlin() error { + buf, err := os.ReadFile(merlinDNSMasqPostConf) + if err != nil && !os.IsNotExist(err) { + return err + } + // Restore dnsmasq post conf file. + if err := os.WriteFile(merlinDNSMasqPostConfPath, merlinParsePostConf(buf), 0750); err != nil { + return err + } + // Restart dnsmasq service. + if err := merlinRestartDNSMasq(); err != nil { + return err + } + return nil +} + +func postInstallMerlin() error { + return nil +} + +func merlinRestartDNSMasq() error { + if out, err := exec.Command("service", "restart_dnsmasq").CombinedOutput(); err != nil { + return fmt.Errorf("restart_dnsmasq: %s, %w", string(out), err) + } + return nil +} + +func merlinParsePostConf(buf []byte) []byte { + if len(buf) == 0 { + return nil + } + parts := bytes.Split(buf, []byte(merlinDNSMasqPostConfMarker)) + if len(parts) != 1 { + return bytes.TrimLeftFunc(parts[1], unicode.IsSpace) + } + return buf +} diff --git a/internal/router/merlin_test.go b/internal/router/merlin_test.go new file mode 100644 index 0000000..2a3c241 --- /dev/null +++ b/internal/router/merlin_test.go @@ -0,0 +1,38 @@ +package router + +import ( + "bytes" + "strings" + "testing" +) + +func Test_merlinParsePostConf(t *testing.T) { + origContent := "# foo" + data := strings.Join([]string{ + merlinDNSMasqPostConf, + "\n", + merlinDNSMasqPostConfMarker, + "\n", + }, "\n") + + tests := []struct { + name string + data string + expected string + }{ + {"empty", "", ""}, + {"no ctrld", origContent, origContent}, + {"ctrld with data", data + origContent, origContent}, + {"ctrld without data", data, ""}, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + //t.Parallel() + if got := merlinParsePostConf([]byte(tc.data)); !bytes.Equal(got, []byte(tc.expected)) { + t.Errorf("unexpected result, want: %q, got: %q", tc.expected, string(got)) + } + }) + } +} diff --git a/internal/router/router.go b/internal/router/router.go index 9987314..bca465c 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -40,9 +40,11 @@ func Configure(c *ctrld.Config) error { switch name { case DDWrt: return setupDDWrt() + case Merlin: + return setupMerlin() case OpenWrt: return setupOpenWrt() - case Merlin, Ubios: + case Ubios: default: return ErrNotSupported } @@ -72,9 +74,12 @@ func PostInstall() error { switch name { case DDWrt: return postInstallDDWrt() + case Merlin: + return postInstallMerlin() case OpenWrt: return postInstallOpenWrt() - case Merlin, Ubios: + + case Ubios: } return nil } @@ -83,11 +88,13 @@ func PostInstall() error { func Cleanup() error { name := Name() switch name { - case OpenWrt: - return cleanupOpenWrt() case DDWrt: return cleanupDDWrt() - case Merlin, Ubios: + case Merlin: + return cleanupMerlin() + case OpenWrt: + return cleanupOpenWrt() + case Ubios: } return nil } @@ -98,7 +105,9 @@ func ListenAddress() string { switch name { case DDWrt, OpenWrt: return "127.0.0.1:5353" - case Merlin, Ubios: + case Merlin: + return "127.0.0.1:5354" + case Ubios: } return "" } diff --git a/internal/router/service.go b/internal/router/service.go index 8845564..7c8bb40 100644 --- a/internal/router/service.go +++ b/internal/router/service.go @@ -7,16 +7,27 @@ import ( ) func init() { - system := &linuxSystemService{ - name: "ddwrt", - detect: func() bool { return Name() == DDWrt }, - interactive: func() bool { - is, _ := isInteractive() - return is + systems := []service.System{ + &linuxSystemService{ + name: "ddwrt", + detect: func() bool { return Name() == DDWrt }, + interactive: func() bool { + is, _ := isInteractive() + return is + }, + new: newddwrtService, + }, + &linuxSystemService{ + name: "merlin", + detect: func() bool { return Name() == Merlin }, + interactive: func() bool { + is, _ := isInteractive() + return is + }, + new: newMerlinService, }, - new: newddwrtService, } - systems := append([]service.System{system}, service.AvailableSystems()...) + systems = append(systems, service.AvailableSystems()...) service.ChooseSystem(systems...) } diff --git a/internal/router/service_ddwrt.go b/internal/router/service_ddwrt.go index 097b3e6..953035e 100644 --- a/internal/router/service_ddwrt.go +++ b/internal/router/service_ddwrt.go @@ -157,7 +157,11 @@ func (s *ddwrtSvc) Run() (err error) { return err } - var sigChan = make(chan os.Signal, 3) + if interactice, _ := isInteractive(); !interactice { + signal.Ignore(syscall.SIGHUP) + signal.Ignore(sigCHLD) + } + var sigChan = make(chan os.Signal, 2) signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) <-sigChan diff --git a/internal/router/service_merlin.go b/internal/router/service_merlin.go new file mode 100644 index 0000000..3c5e2de --- /dev/null +++ b/internal/router/service_merlin.go @@ -0,0 +1,308 @@ +package router + +import ( + "bytes" + "errors" + "fmt" + "os" + "os/exec" + "os/signal" + "path/filepath" + "strings" + "syscall" + "text/template" + + "github.com/kardianos/service" +) + +const merlinJFFSScriptPath = "/jffs/scripts/services-start" + +type merlinSvc struct { + i service.Interface + platform string + *service.Config +} + +func newMerlinService(i service.Interface, platform string, c *service.Config) (service.Service, error) { + s := &merlinSvc{ + i: i, + platform: platform, + Config: c, + } + return s, nil +} + +func (s *merlinSvc) String() string { + if len(s.DisplayName) > 0 { + return s.DisplayName + } + return s.Name +} + +func (s *merlinSvc) Platform() string { + return s.platform +} + +func (s *merlinSvc) configPath() string { + path, err := os.Executable() + if err != nil { + return "" + } + return path + ".startup" +} + +func (s *merlinSvc) template() *template.Template { + return template.Must(template.New("").Parse(merlinSvcScript)) +} + +func (s *merlinSvc) Install() error { + exePath, err := os.Executable() + if err != nil { + return err + } + + if !strings.HasPrefix(exePath, "/jffs/") { + return errors.New("could not install service outside /jffs") + } + if _, err := nvram("set", "jffs2_scripts=1"); err != nil { + return err + } + if _, err := nvram("commit"); err != nil { + return err + } + + confPath := s.configPath() + if _, err := os.Stat(confPath); err == nil { + return fmt.Errorf("already installed: %s", confPath) + } + + var to = &struct { + *service.Config + Path string + }{ + s.Config, + exePath, + } + + f, err := os.Create(confPath) + if err != nil { + return fmt.Errorf("os.Create: %w", err) + } + defer f.Close() + + if err := s.template().Execute(f, to); err != nil { + return fmt.Errorf("s.template.Execute: %w", err) + } + + if err = os.Chmod(confPath, 0755); err != nil { + return fmt.Errorf("os.Chmod: startup script: %w", err) + } + + if err := os.MkdirAll(filepath.Dir(merlinJFFSScriptPath), 0755); err != nil { + return fmt.Errorf("os.MkdirAll: %w", err) + } + if _, err := os.Stat(merlinJFFSScriptPath); os.IsNotExist(err) { + if err := os.WriteFile(merlinJFFSScriptPath, []byte("#!/bin/sh\n"), 0755); err != nil { + return err + } + } + if err := os.Chmod(merlinJFFSScriptPath, 0755); err != nil { + return fmt.Errorf("os.Chmod: jffs script: %w", err) + } + + tmpScript, err := os.CreateTemp("", "ctrld_install") + if err != nil { + return fmt.Errorf("os.CreateTemp: %w", err) + } + defer os.Remove(tmpScript.Name()) + defer tmpScript.Close() + + if _, err := tmpScript.WriteString(merlinAddStartupScript); err != nil { + return fmt.Errorf("tmpScript.WriteString: %w", err) + } + if err := tmpScript.Close(); err != nil { + return fmt.Errorf("tmpScript.Close: %w", err) + } + if err := exec.Command("sh", tmpScript.Name(), s.configPath()+" start", merlinJFFSScriptPath).Run(); err != nil { + return fmt.Errorf("exec.Command: add startup script: %w", err) + } + + return nil +} + +func (s *merlinSvc) Uninstall() error { + if err := os.Remove(s.configPath()); err != nil { + return fmt.Errorf("os.Remove: %w", err) + } + tmpScript, err := os.CreateTemp("", "ctrld_uninstall") + if err != nil { + return fmt.Errorf("os.CreateTemp: %w", err) + } + defer os.Remove(tmpScript.Name()) + defer tmpScript.Close() + + if _, err := tmpScript.WriteString(merlinRemoveStartupScript); err != nil { + return fmt.Errorf("tmpScript.WriteString: %w", err) + } + if err := tmpScript.Close(); err != nil { + return fmt.Errorf("tmpScript.Close: %w", err) + } + if err := exec.Command("sh", tmpScript.Name(), s.configPath()+" start", merlinJFFSScriptPath).Run(); err != nil { + return fmt.Errorf("exec.Command: %w", err) + } + return nil +} + +func (s *merlinSvc) Logger(errs chan<- error) (service.Logger, error) { + if service.Interactive() { + return service.ConsoleLogger, nil + } + return s.SystemLogger(errs) +} + +func (s *merlinSvc) SystemLogger(errs chan<- error) (service.Logger, error) { + return newSysLogger(s.Name, errs) +} + +func (s *merlinSvc) Run() (err error) { + err = s.i.Start(s) + if err != nil { + return err + } + + if interactice, _ := isInteractive(); !interactice { + signal.Ignore(syscall.SIGHUP) + signal.Ignore(sigCHLD) + } + + var sigChan = make(chan os.Signal, 3) + signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) + <-sigChan + + return s.i.Stop(s) +} + +func (s *merlinSvc) Status() (service.Status, error) { + if _, err := os.Stat(s.configPath()); os.IsNotExist(err) { + return service.StatusUnknown, service.ErrNotInstalled + } + out, err := exec.Command(s.configPath(), "status").CombinedOutput() + if err != nil { + return service.StatusUnknown, err + } + switch string(bytes.TrimSpace(out)) { + case "running": + return service.StatusRunning, nil + default: + return service.StatusStopped, nil + } +} + +func (s *merlinSvc) Start() error { + return exec.Command(s.configPath(), "start").Run() +} + +func (s *merlinSvc) Stop() error { + return exec.Command(s.configPath(), "stop").Run() +} + +func (s *merlinSvc) Restart() error { + err := s.Stop() + if err != nil { + return err + } + return s.Start() +} + +const merlinSvcScript = `#!/bin/sh + +name="{{.Name}}" +cmd="{{.Path}}{{range .Arguments}} {{.}}{{end}}" +pid_file="/tmp/$name.pid" + +get_pid() { + cat "$pid_file" +} + +is_running() { + [ -f "$pid_file" ] && ps | grep -q "^ *$(get_pid) " +} + +case "$1" in + start) + if is_running; then + logger -c "Already started" + else + logger -c "Starting $name" + if [ -f /rom/ca-bundle.crt ]; then + # For John’s fork + export SSL_CERT_FILE=/rom/ca-bundle.crt + fi + $cmd & + echo $! > "$pid_file" + chmod 600 "$pid_file" + if ! is_running; then + logger -c "Failed to start $name" + exit 1 + fi + fi + ;; + stop) + if is_running; then + logger -c "Stopping $name..." + kill "$(get_pid)" + for _ in 1 2 3 4 5; do + if ! is_running; then + logger -c "stopped" + if [ -f "$pid_file" ]; then + rm "$pid_file" + fi + exit 0 + fi + printf "." + sleep 2 + done + logger -c "failed to stop $name" + exit 1 + fi + exit 1 + ;; + restart) + $0 stop + $0 start + ;; + status) + if is_running; then + echo "running" + else + echo "stopped" + exit 1 + fi + ;; + *) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 + ;; +esac +exit 0 +` + +const merlinAddStartupScript = `#!/bin/sh + +line=$1 +file=$2 + +. /usr/sbin/helper.sh + +pc_append "$line" "$file" +` + +const merlinRemoveStartupScript = `#!/bin/sh + +line=$1 +file=$2 + +. /usr/sbin/helper.sh + +pc_delete "$line" "$file" +` diff --git a/internal/router/signal.go b/internal/router/signal.go new file mode 100644 index 0000000..f6f11ed --- /dev/null +++ b/internal/router/signal.go @@ -0,0 +1,7 @@ +//go:build !windows + +package router + +import "syscall" + +const sigCHLD = syscall.SIGCHLD diff --git a/internal/router/signal_windows.go b/internal/router/signal_windows.go new file mode 100644 index 0000000..6526575 --- /dev/null +++ b/internal/router/signal_windows.go @@ -0,0 +1,5 @@ +package router + +import "syscall" + +const sigCHLD = syscall.SIGHUP diff --git a/internal/router/syslog.go b/internal/router/syslog.go new file mode 100644 index 0000000..008bbeb --- /dev/null +++ b/internal/router/syslog.go @@ -0,0 +1,49 @@ +//go:build linux || darwin || freebsd + +package router + +import ( + "fmt" + "log/syslog" + + "github.com/kardianos/service" +) + +func newSysLogger(name string, errs chan<- error) (service.Logger, error) { + w, err := syslog.New(syslog.LOG_INFO, name) + if err != nil { + return nil, err + } + return sysLogger{w, errs}, nil +} + +type sysLogger struct { + *syslog.Writer + errs chan<- error +} + +func (s sysLogger) send(err error) error { + if err != nil && s.errs != nil { + s.errs <- err + } + return err +} + +func (s sysLogger) Error(v ...interface{}) error { + return s.send(s.Writer.Err(fmt.Sprint(v...))) +} +func (s sysLogger) Warning(v ...interface{}) error { + return s.send(s.Writer.Warning(fmt.Sprint(v...))) +} +func (s sysLogger) Info(v ...interface{}) error { + return s.send(s.Writer.Info(fmt.Sprint(v...))) +} +func (s sysLogger) Errorf(format string, a ...interface{}) error { + return s.send(s.Writer.Err(fmt.Sprintf(format, a...))) +} +func (s sysLogger) Warningf(format string, a ...interface{}) error { + return s.send(s.Writer.Warning(fmt.Sprintf(format, a...))) +} +func (s sysLogger) Infof(format string, a ...interface{}) error { + return s.send(s.Writer.Info(fmt.Sprintf(format, a...))) +} diff --git a/internal/router/syslog_windows.go b/internal/router/syslog_windows.go new file mode 100644 index 0000000..ecac969 --- /dev/null +++ b/internal/router/syslog_windows.go @@ -0,0 +1,7 @@ +package router + +import "github.com/kardianos/service" + +func newSysLogger(name string, errs chan<- error) (service.Logger, error) { + return service.ConsoleLogger, nil +} From f5ef9b917eb286956d10b7a4db95c1802841bdbd Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 18 Apr 2023 21:16:12 +0700 Subject: [PATCH 08/55] all: implement router setup for ubios --- internal/router/dnsmasq.go | 2 +- internal/router/router.go | 13 +- internal/router/service.go | 22 +++ internal/router/service_ubios.go | 320 +++++++++++++++++++++++++++++++ internal/router/ubios.go | 55 ++++++ 5 files changed, 402 insertions(+), 10 deletions(-) create mode 100644 internal/router/service_ubios.go create mode 100644 internal/router/ubios.go diff --git a/internal/router/dnsmasq.go b/internal/router/dnsmasq.go index 9c6fd2b..d90854a 100644 --- a/internal/router/dnsmasq.go +++ b/internal/router/dnsmasq.go @@ -2,7 +2,7 @@ package router const dnsMasqConfigContent = `# GENERATED BY ctrld - DO NOT MODIFY no-resolv -server=127.0.0.1#5353 +server=127.0.0.1#5354 ` const merlinDNSMasqPostConfPath = "/jffs/scripts/dnsmasq.postconf" diff --git a/internal/router/router.go b/internal/router/router.go index bca465c..13ec295 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -3,7 +3,6 @@ package router import ( "bytes" "errors" - "fmt" "os" "os/exec" "sync/atomic" @@ -45,12 +44,10 @@ func Configure(c *ctrld.Config) error { case OpenWrt: return setupOpenWrt() case Ubios: + return setupUbiOS() default: return ErrNotSupported } - // TODO: implement all supported platforms. - fmt.Printf("Configuring router for: %s\n", name) - return nil } // ConfigureService performs necessary setup for running ctrld as a service on router. @@ -78,8 +75,8 @@ func PostInstall() error { return postInstallMerlin() case OpenWrt: return postInstallOpenWrt() - case Ubios: + return postInstallUbiOS() } return nil } @@ -95,6 +92,7 @@ func Cleanup() error { case OpenWrt: return cleanupOpenWrt() case Ubios: + return cleanupUbiOS() } return nil } @@ -103,11 +101,8 @@ func Cleanup() error { func ListenAddress() string { name := Name() switch name { - case DDWrt, OpenWrt: - return "127.0.0.1:5353" - case Merlin: + case DDWrt, Merlin, OpenWrt, Ubios: return "127.0.0.1:5354" - case Ubios: } return "" } diff --git a/internal/router/service.go b/internal/router/service.go index 7c8bb40..e404a2d 100644 --- a/internal/router/service.go +++ b/internal/router/service.go @@ -1,7 +1,9 @@ package router import ( + "bytes" "os" + "os/exec" "github.com/kardianos/service" ) @@ -26,6 +28,26 @@ func init() { }, new: newMerlinService, }, + &linuxSystemService{ + name: "ubios", + detect: func() bool { + if Name() != Ubios { + return false + } + out, err := exec.Command("ubnt-device-info", "firmware").CombinedOutput() + if err == nil { + // For v2/v3, UbiOS use a Debian base with systemd, so it is not + // necessary to use custom implementation for supporting init system. + return bytes.HasPrefix(out, []byte("1.")) + } + return true + }, + interactive: func() bool { + is, _ := isInteractive() + return is + }, + new: newUbiosService, + }, } systems = append(systems, service.AvailableSystems()...) service.ChooseSystem(systems...) diff --git a/internal/router/service_ubios.go b/internal/router/service_ubios.go new file mode 100644 index 0000000..a779590 --- /dev/null +++ b/internal/router/service_ubios.go @@ -0,0 +1,320 @@ +package router + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "os/signal" + "path/filepath" + "strings" + "syscall" + "text/template" + "time" + + "github.com/kardianos/service" +) + +// This is a copy of https://github.com/kardianos/service/blob/v1.2.1/service_sysv_linux.go, +// with modification for supporting ubios v1 init system. + +type ubiosSvc struct { + i service.Interface + platform string + *service.Config +} + +func newUbiosService(i service.Interface, platform string, c *service.Config) (service.Service, error) { + s := &ubiosSvc{ + i: i, + platform: platform, + Config: c, + } + return s, nil +} + +func (s *ubiosSvc) String() string { + if len(s.DisplayName) > 0 { + return s.DisplayName + } + return s.Name +} + +func (s *ubiosSvc) Platform() string { + return s.platform +} + +func (s *ubiosSvc) configPath() string { + return "/etc/init.d/" + s.Config.Name +} + +func (s *ubiosSvc) execPath() (string, error) { + if len(s.Executable) != 0 { + return filepath.Abs(s.Executable) + } + return os.Executable() +} + +func (s *ubiosSvc) template() *template.Template { + return template.Must(template.New("").Funcs(tf).Parse(ubiosSvcScript)) +} + +func (s *ubiosSvc) Install() error { + confPath := s.configPath() + if _, err := os.Stat(confPath); err == nil { + return fmt.Errorf("init already exists: %s", confPath) + } + + f, err := os.Create(confPath) + if err != nil { + return fmt.Errorf("failed to create config path: %w", err) + } + defer f.Close() + + path, err := s.execPath() + if err != nil { + return fmt.Errorf("failed to get exec path: %w", err) + } + + var to = &struct { + *service.Config + Path string + DnsMasqConfPath string + }{ + s.Config, + path, + ubiosDNSMasqConfigPath, + } + + if err := s.template().Execute(f, to); err != nil { + return fmt.Errorf("failed to create init script: %w", err) + } + + if err := f.Close(); err != nil { + return fmt.Errorf("failed to save init script: %w", err) + } + + if err = os.Chmod(confPath, 0755); err != nil { + return fmt.Errorf("failed to set init script executable: %w", err) + } + + // Enable on boot + script, err := os.CreateTemp("", "ctrld_boot.service") + if err != nil { + return fmt.Errorf("failed to create boot service tmp file: %w", err) + } + defer script.Close() + + svcConfig := *to.Config + svcConfig.Arguments = os.Args[1:] + to.Config = &svcConfig + if err := template.Must(template.New("").Funcs(tf).Parse(ubiosBootSystemdService)).Execute(script, &to); err != nil { + return fmt.Errorf("failed to create boot service file: %w", err) + } + if err := script.Close(); err != nil { + return fmt.Errorf("failed to save boot service file: %w", err) + } + + // Copy the boot script to container and start. + cmd := exec.Command("podman", "cp", "--pause=false", script.Name(), "unifi-os:/lib/systemd/system/ctrld-boot.service") + if out, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("failed to copy boot script, out: %s, err: %v", string(out), err) + } + cmd = exec.Command("podman", "exec", "unifi-os", "systemctl", "enable", "--now", "ctrld-boot.service") + if out, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("failed to start ctrld boot script, out: %s, err: %v", string(out), err) + } + return nil +} + +func (s *ubiosSvc) Uninstall() error { + if err := os.Remove(s.configPath()); err != nil { + return err + } + return nil +} + +func (s *ubiosSvc) Logger(errs chan<- error) (service.Logger, error) { + if service.Interactive() { + return service.ConsoleLogger, nil + } + return s.SystemLogger(errs) +} + +func (s *ubiosSvc) SystemLogger(errs chan<- error) (service.Logger, error) { + return newSysLogger(s.Name, errs) +} + +func (s *ubiosSvc) Run() (err error) { + err = s.i.Start(s) + if err != nil { + return err + } + + if interactice, _ := isInteractive(); !interactice { + signal.Ignore(syscall.SIGHUP) + signal.Ignore(sigCHLD) + } + + var sigChan = make(chan os.Signal, 3) + signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) + <-sigChan + + return s.i.Stop(s) +} + +func (s *ubiosSvc) Status() (service.Status, error) { + if _, err := os.Stat(s.configPath()); os.IsNotExist(err) { + return service.StatusUnknown, service.ErrNotInstalled + } + out, err := exec.Command(s.configPath(), "status").CombinedOutput() + if err != nil { + return service.StatusUnknown, err + } + switch string(bytes.TrimSpace(out)) { + case "Running": + return service.StatusRunning, nil + default: + return service.StatusStopped, nil + } +} + +func (s *ubiosSvc) Start() error { + return exec.Command(s.configPath(), "start").Run() +} + +func (s *ubiosSvc) Stop() error { + return exec.Command(s.configPath(), "stop").Run() +} + +func (s *ubiosSvc) Restart() error { + err := s.Stop() + if err != nil { + return err + } + time.Sleep(50 * time.Millisecond) + return s.Start() +} + +const ubiosBootSystemdService = `[Unit] +Description=Run ctrld On Startup UDM +Wants=network-online.target +After=network-online.target +StartLimitIntervalSec=500 +StartLimitBurst=5 + +[Service] +Restart=on-failure +RestartSec=5s +ExecStart=/sbin/ssh-proxy '[ -f "{{.DnsMasqConfPath}}" ] || {{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}' +RemainAfterExit=true +[Install] +WantedBy=multi-user.target +` + +const ubiosSvcScript = `#!/bin/sh +# For RedHat and cousins: +# chkconfig: - 99 01 +# description: {{.Description}} +# processname: {{.Path}} + +### BEGIN INIT INFO +# Provides: {{.Path}} +# Required-Start: +# Required-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: {{.DisplayName}} +# Description: {{.Description}} +### END INIT INFO + +cmd="{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}" + +name=$(basename $(readlink -f $0)) +pid_file="/var/run/$name.pid" +stdout_log="/var/log/$name.log" +stderr_log="/var/log/$name.err" + +[ -e /etc/sysconfig/$name ] && . /etc/sysconfig/$name + +get_pid() { + cat "$pid_file" +} + +is_running() { + [ -f "$pid_file" ] && cat /proc/$(get_pid)/stat > /dev/null 2>&1 +} + +case "$1" in + start) + if is_running; then + echo "Already started" + else + echo "Starting $name" + {{if .WorkingDirectory}}cd '{{.WorkingDirectory}}'{{end}} + $cmd >> "$stdout_log" 2>> "$stderr_log" & + echo $! > "$pid_file" + if ! is_running; then + echo "Unable to start, see $stdout_log and $stderr_log" + exit 1 + fi + fi + ;; + stop) + if is_running; then + echo -n "Stopping $name.." + kill $(get_pid) + for i in $(seq 1 10) + do + if ! is_running; then + break + fi + echo -n "." + sleep 1 + done + echo + if is_running; then + echo "Not stopped; may still be shutting down or shutdown may have failed" + exit 1 + else + echo "Stopped" + if [ -f "$pid_file" ]; then + rm "$pid_file" + fi + fi + else + echo "Not running" + fi + ;; + restart) + $0 stop + if is_running; then + echo "Unable to stop, will not attempt to start" + exit 1 + fi + $0 start + ;; + status) + if is_running; then + echo "Running" + else + echo "Stopped" + exit 1 + fi + ;; + *) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 + ;; +esac +exit 0 +` + +var tf = map[string]interface{}{ + "cmd": func(s string) string { + return `"` + strings.Replace(s, `"`, `\"`, -1) + `"` + }, + "cmdEscape": func(s string) string { + return strings.Replace(s, " ", `\x20`, -1) + }, +} diff --git a/internal/router/ubios.go b/internal/router/ubios.go new file mode 100644 index 0000000..8affb33 --- /dev/null +++ b/internal/router/ubios.go @@ -0,0 +1,55 @@ +package router + +import ( + "bytes" + "os" + "strconv" +) + +const ( + ubiosDNSMasqConfigPath = "/run/dnsmasq.conf.d/zzzctrld.conf" +) + +func setupUbiOS() error { + // Disable dnsmasq as DNS server. + if err := os.WriteFile(ubiosDNSMasqConfigPath, []byte(dnsMasqConfigContent), 0600); err != nil { + return err + } + // Restart dnsmasq service. + if err := ubiosRestartDNSMasq(); err != nil { + return err + } + return nil +} + +func cleanupUbiOS() error { + // Remove the custom dnsmasq config + if err := os.Remove(ubiosDNSMasqConfigPath); err != nil { + return err + } + // Restart dnsmasq service. + if err := ubiosRestartDNSMasq(); err != nil { + return err + } + return nil +} + +func postInstallUbiOS() error { + return nil +} + +func ubiosRestartDNSMasq() error { + buf, err := os.ReadFile("/run/dnsmasq.pid") + if err != nil { + return err + } + pid, err := strconv.ParseUint(string(bytes.TrimSpace(buf)), 10, 64) + if err != nil { + return err + } + proc, err := os.FindProcess(int(pid)) + if err != nil { + return err + } + return proc.Kill() +} From ccdb2a3f705be9014ac5ad3859e4ad9cd8bb4d7c Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 17 Apr 2023 23:48:49 +0700 Subject: [PATCH 09/55] Tweak log message for policy logging --- cmd/ctrld/dns_proxy.go | 66 +++++++++++++++++++++++-------------- cmd/ctrld/dns_proxy_test.go | 15 +++++---- cmd/ctrld/main_test.go | 16 +++++++++ 3 files changed, 66 insertions(+), 31 deletions(-) create mode 100644 cmd/ctrld/main_test.go diff --git a/cmd/ctrld/dns_proxy.go b/cmd/ctrld/dns_proxy.go index 0821813..757c7ed 100644 --- a/cmd/ctrld/dns_proxy.go +++ b/cmd/ctrld/dns_proxy.go @@ -105,6 +105,13 @@ func (p *prog) serveDNS(listenerNum string) error { return g.Wait() } +// upstreamFor returns the list of upstreams for resolving the given domain, +// matching by policies defined in the listener config. The second return value +// reports whether the domain matches the policy. +// +// Though domain policy has higher priority than network policy, it is still +// processed later, because policy logging want to know whether a network rule +// is disregarded in favor of the domain level rule. func (p *prog) upstreamFor(ctx context.Context, defaultUpstreamNum string, lc *ctrld.ListenerConfig, addr net.Addr, domain string) ([]string, bool) { upstreams := []string{"upstream." + defaultUpstreamNum} matchedPolicy := "no policy" @@ -128,11 +135,43 @@ func (p *prog) upstreamFor(ctx context.Context, defaultUpstreamNum string, lc *c upstreams = append([]string(nil), policyUpstreams...) } + var networkTargets []string + var sourceIP net.IP + switch addr := addr.(type) { + case *net.UDPAddr: + sourceIP = addr.IP + case *net.TCPAddr: + sourceIP = addr.IP + } + +networkRules: + for _, rule := range lc.Policy.Networks { + for source, targets := range rule { + networkNum := strings.TrimPrefix(source, "network.") + nc := p.cfg.Network[networkNum] + if nc == nil { + continue + } + for _, ipNet := range nc.IPNets { + if ipNet.Contains(sourceIP) { + matchedPolicy = lc.Policy.Name + matchedNetwork = source + networkTargets = targets + matched = true + break networkRules + } + } + } + } + for _, rule := range lc.Policy.Rules { // There's only one entry per rule, config validation ensures this. for source, targets := range rule { if source == domain || wildcardMatches(source, domain) { matchedPolicy = lc.Policy.Name + if len(networkTargets) > 0 { + matchedNetwork += " (unenforced)" + } matchedRule = source do(targets) matched = true @@ -141,31 +180,8 @@ func (p *prog) upstreamFor(ctx context.Context, defaultUpstreamNum string, lc *c } } - var sourceIP net.IP - switch addr := addr.(type) { - case *net.UDPAddr: - sourceIP = addr.IP - case *net.TCPAddr: - sourceIP = addr.IP - } - for _, rule := range lc.Policy.Networks { - for source, targets := range rule { - networkNum := strings.TrimPrefix(source, "network.") - nc := p.cfg.Network[networkNum] - if nc == nil { - continue - } - - for _, ipNet := range nc.IPNets { - if ipNet.Contains(sourceIP) { - matchedPolicy = lc.Policy.Name - matchedNetwork = source - do(targets) - matched = true - return upstreams, matched - } - } - } + if matched { + do(networkTargets) } return upstreams, matched diff --git a/cmd/ctrld/dns_proxy_test.go b/cmd/ctrld/dns_proxy_test.go index 82c0c95..6d64346 100644 --- a/cmd/ctrld/dns_proxy_test.go +++ b/cmd/ctrld/dns_proxy_test.go @@ -86,17 +86,17 @@ func Test_prog_upstreamFor(t *testing.T) { domain string upstreams []string matched bool + testLogMsg string }{ - {"Policy map matches", "192.168.0.1:0", "0", prog.cfg.Listener["0"], "abc.xyz", []string{"upstream.1", "upstream.0"}, true}, - {"Policy split matches", "192.168.0.1:0", "0", prog.cfg.Listener["0"], "abc.ru", []string{"upstream.1"}, true}, - {"Policy map for other network matches", "192.168.1.2:0", "0", prog.cfg.Listener["0"], "abc.xyz", []string{"upstream.0"}, true}, - {"No policy map for listener", "192.168.1.2:0", "1", prog.cfg.Listener["1"], "abc.ru", []string{"upstream.1"}, false}, + {"Policy map matches", "192.168.0.1:0", "0", prog.cfg.Listener["0"], "abc.xyz", []string{"upstream.1", "upstream.0"}, true, ""}, + {"Policy split matches", "192.168.0.1:0", "0", prog.cfg.Listener["0"], "abc.ru", []string{"upstream.1"}, true, ""}, + {"Policy map for other network matches", "192.168.1.2:0", "0", prog.cfg.Listener["0"], "abc.xyz", []string{"upstream.0"}, true, ""}, + {"No policy map for listener", "192.168.1.2:0", "1", prog.cfg.Listener["1"], "abc.ru", []string{"upstream.1"}, false, ""}, + {"unenforced loging", "192.168.1.2:0", "0", prog.cfg.Listener["0"], "abc.ru", []string{"upstream.1"}, true, "My Policy, network.1 (unenforced), *.ru -> [upstream.1]"}, } for _, tc := range tests { - tc := tc t.Run(tc.name, func(t *testing.T) { - t.Parallel() for _, network := range []string{"udp", "tcp"} { var ( addr net.Addr @@ -114,6 +114,9 @@ func Test_prog_upstreamFor(t *testing.T) { upstreams, matched := prog.upstreamFor(ctx, tc.defaultUpstreamNum, tc.lc, addr, tc.domain) assert.Equal(t, tc.matched, matched) assert.Equal(t, tc.upstreams, upstreams) + if tc.testLogMsg != "" { + assert.Contains(t, logOutput.String(), tc.testLogMsg) + } } }) } diff --git a/cmd/ctrld/main_test.go b/cmd/ctrld/main_test.go new file mode 100644 index 0000000..2a2e079 --- /dev/null +++ b/cmd/ctrld/main_test.go @@ -0,0 +1,16 @@ +package main + +import ( + "os" + "strings" + "testing" + + "github.com/rs/zerolog" +) + +var logOutput strings.Builder + +func TestMain(m *testing.M) { + mainLog = zerolog.New(&logOutput) + os.Exit(m.Run()) +} From 6c55d8f139e645d153b92670ea2b835245845bc4 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 20 Apr 2023 09:55:57 +0700 Subject: [PATCH 10/55] internal/router: remove ctrld-boot service when uninstall --- internal/router/service_ubios.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/internal/router/service_ubios.go b/internal/router/service_ubios.go index a779590..5812eda 100644 --- a/internal/router/service_ubios.go +++ b/internal/router/service_ubios.go @@ -131,6 +131,23 @@ func (s *ubiosSvc) Uninstall() error { if err := os.Remove(s.configPath()); err != nil { return err } + // Remove ctrld-boot service inside unifi-os container. + cmd := exec.Command("podman", "exec", "unifi-os", "systemctl", "disable", "ctrld-boot.service") + if out, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("failed to disable ctrld-boot service, out: %s, err: %v", string(out), err) + } + cmd = exec.Command("podman", "exec", "unifi-os", "rm", "/lib/systemd/system/ctrld-boot.service") + if out, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("failed to remove ctrld-boot service file, out: %s, err: %v", string(out), err) + } + cmd = exec.Command("podman", "exec", "unifi-os", "systemctl", "daemon-reload") + if out, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("failed to reload systemd service, out: %s, err: %v", string(out), err) + } + cmd = exec.Command("podman", "exec", "unifi-os", "systemctl", "reset-failed") + if out, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("failed to reset-failed systemd service, out: %s, err: %v", string(out), err) + } return nil } From 21c8b9f8e7bd3d87dd9c8763d588ba322de88a15 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 20 Apr 2023 23:25:40 +0700 Subject: [PATCH 11/55] Revert ignoring SIGCHLD Using signal.Ignore causes exec.Command failed with no child process error. --- internal/router/service_ddwrt.go | 3 +-- internal/router/service_merlin.go | 3 +-- internal/router/service_ubios.go | 1 - internal/router/signal.go | 7 ------- internal/router/signal_windows.go | 5 ----- 5 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 internal/router/signal.go delete mode 100644 internal/router/signal_windows.go diff --git a/internal/router/service_ddwrt.go b/internal/router/service_ddwrt.go index 953035e..ac177f9 100644 --- a/internal/router/service_ddwrt.go +++ b/internal/router/service_ddwrt.go @@ -159,9 +159,8 @@ func (s *ddwrtSvc) Run() (err error) { if interactice, _ := isInteractive(); !interactice { signal.Ignore(syscall.SIGHUP) - signal.Ignore(sigCHLD) } - var sigChan = make(chan os.Signal, 2) + var sigChan = make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) <-sigChan diff --git a/internal/router/service_merlin.go b/internal/router/service_merlin.go index 3c5e2de..7f40b0d 100644 --- a/internal/router/service_merlin.go +++ b/internal/router/service_merlin.go @@ -172,10 +172,9 @@ func (s *merlinSvc) Run() (err error) { if interactice, _ := isInteractive(); !interactice { signal.Ignore(syscall.SIGHUP) - signal.Ignore(sigCHLD) } - var sigChan = make(chan os.Signal, 3) + var sigChan = make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) <-sigChan diff --git a/internal/router/service_ubios.go b/internal/router/service_ubios.go index 5812eda..5c4d99d 100644 --- a/internal/router/service_ubios.go +++ b/internal/router/service_ubios.go @@ -170,7 +170,6 @@ func (s *ubiosSvc) Run() (err error) { if interactice, _ := isInteractive(); !interactice { signal.Ignore(syscall.SIGHUP) - signal.Ignore(sigCHLD) } var sigChan = make(chan os.Signal, 3) diff --git a/internal/router/signal.go b/internal/router/signal.go deleted file mode 100644 index f6f11ed..0000000 --- a/internal/router/signal.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !windows - -package router - -import "syscall" - -const sigCHLD = syscall.SIGCHLD diff --git a/internal/router/signal_windows.go b/internal/router/signal_windows.go deleted file mode 100644 index 6526575..0000000 --- a/internal/router/signal_windows.go +++ /dev/null @@ -1,5 +0,0 @@ -package router - -import "syscall" - -const sigCHLD = syscall.SIGHUP From d3d08022cc55929e274ba50194e98b870dd5013d Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 21 Apr 2023 10:28:23 +0700 Subject: [PATCH 12/55] cmd/ctrld: restoring DNS on darwin before stop Otherwise, we experiment with ctrld slow start after rebooting, because the network check continuously report failed status even the network state is up. Restoring the DNS before stopping, we leave the network state as default, as long as ctrld starts, the DNS is configured again. --- cmd/ctrld/prog.go | 1 + cmd/ctrld/prog_darwin.go | 23 +++++++++++++++++++++++ cmd/ctrld/prog_freebsd.go | 2 ++ cmd/ctrld/prog_linux.go | 2 ++ cmd/ctrld/prog_others.go | 4 +++- 5 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 cmd/ctrld/prog_darwin.go diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index 7388983..08251ef 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -138,6 +138,7 @@ func (p *prog) Stop(s service.Service) error { mainLog.Error().Err(err).Msg("de-allocate ip failed") return err } + p.preStop() mainLog.Info().Msg("Service stopped") close(p.stopCh) return nil diff --git a/cmd/ctrld/prog_darwin.go b/cmd/ctrld/prog_darwin.go new file mode 100644 index 0000000..2b82eb5 --- /dev/null +++ b/cmd/ctrld/prog_darwin.go @@ -0,0 +1,23 @@ +package main + +import ( + "github.com/kardianos/service" +) + +func (p *prog) preRun() { + if !service.Interactive() { + p.setDNS() + } +} + +func setDependencies(svc *service.Config) {} + +func setWorkingDirectory(svc *service.Config, dir string) { + svc.WorkingDirectory = dir +} + +func (p *prog) preStop() { + if !service.Interactive() { + p.resetDNS() + } +} diff --git a/cmd/ctrld/prog_freebsd.go b/cmd/ctrld/prog_freebsd.go index 63d8179..24a90ba 100644 --- a/cmd/ctrld/prog_freebsd.go +++ b/cmd/ctrld/prog_freebsd.go @@ -18,3 +18,5 @@ func setDependencies(svc *service.Config) { } func setWorkingDirectory(svc *service.Config, dir string) {} + +func (p *prog) preStop() {} diff --git a/cmd/ctrld/prog_linux.go b/cmd/ctrld/prog_linux.go index 4ec9416..5cc5d6f 100644 --- a/cmd/ctrld/prog_linux.go +++ b/cmd/ctrld/prog_linux.go @@ -22,3 +22,5 @@ func setDependencies(svc *service.Config) { func setWorkingDirectory(svc *service.Config, dir string) { svc.WorkingDirectory = dir } + +func (p *prog) preStop() {} diff --git a/cmd/ctrld/prog_others.go b/cmd/ctrld/prog_others.go index d790438..b26c0b6 100644 --- a/cmd/ctrld/prog_others.go +++ b/cmd/ctrld/prog_others.go @@ -1,4 +1,4 @@ -//go:build !linux && !freebsd +//go:build !linux && !freebsd && !darwin package main @@ -12,3 +12,5 @@ func setWorkingDirectory(svc *service.Config, dir string) { // WorkingDirectory is not supported on Windows. svc.WorkingDirectory = dir } + +func (p *prog) preStop() {} From d52cd11322257767bf3c4779fe234cb53b4ebad2 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 24 Apr 2023 19:56:01 +0700 Subject: [PATCH 13/55] all: use parallel dialer for connecting upstream/api So we don't have to depend on network stack probing to decide whether ipv4 or ipv6 will be used. While at it, also prevent a race report when doing the same parallel resolving for os resolver, even though this race is harmless. --- cmd/ctrld/cli.go | 2 +- config.go | 69 +++++--------------------------- internal/controld/config.go | 62 +++++----------------------- internal/controld/config_test.go | 2 - internal/net/net.go | 52 ++++++++++++++++++++---- resolver.go | 62 +++++++++++++++++++++++++++- 6 files changed, 127 insertions(+), 122 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 1c7cbd5..5f18933 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -78,7 +78,7 @@ var rootCmd = &cobra.Command{ } func curVersion() string { - if version != "dev" { + if version != "dev" && !strings.HasPrefix(version, "v") { version = "v" + version } if len(commit) > 7 { diff --git a/config.go b/config.go index b30d8a4..7afaac9 100644 --- a/config.go +++ b/config.go @@ -177,71 +177,20 @@ func (uc *UpstreamConfig) SetupBootstrapIP() { // SetupBootstrapIP manually find all available IPs of the upstream. // The first usable IP will be used as bootstrap IP of the upstream. func (uc *UpstreamConfig) setupBootstrapIP(withBootstrapDNS bool) { - bootstrapIP := func(record dns.RR) string { - switch ar := record.(type) { - case *dns.A: - return ar.A.String() - case *dns.AAAA: - return ar.AAAA.String() - } - return "" - } + uc.bootstrapIPs = lookupIP(uc.Domain, uc.Timeout, withBootstrapDNS) + for _, ip := range uc.bootstrapIPs { + if uc.BootstrapIP == "" { + // Remember what's the current IP in bootstrap IPs list, + // so we can select next one upon re-bootstrapping. + uc.nextBootstrapIP.Add(1) - resolver := &osResolver{nameservers: availableNameservers()} - if withBootstrapDNS { - resolver.nameservers = append([]string{net.JoinHostPort(bootstrapDNS, "53")}, resolver.nameservers...) - } - ProxyLog.Debug().Msgf("Resolving %q using bootstrap DNS %q", uc.Domain, resolver.nameservers) - timeoutMs := 2000 - if uc.Timeout > 0 && uc.Timeout < timeoutMs { - timeoutMs = uc.Timeout - } - do := func(dnsType uint16) { - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutMs)*time.Millisecond) - defer cancel() - m := new(dns.Msg) - m.SetQuestion(uc.Domain+".", dnsType) - m.RecursionDesired = true - - r, err := resolver.Resolve(ctx, m) - if err != nil { - ProxyLog.Error().Err(err).Str("type", dns.TypeToString[dnsType]).Msgf("could not resolve domain %s for upstream", uc.Domain) - return - } - if r.Rcode != dns.RcodeSuccess { - ProxyLog.Error().Msgf("could not resolve domain %q, return code: %s", uc.Domain, dns.RcodeToString[r.Rcode]) - return - } - if len(r.Answer) == 0 { - ProxyLog.Error().Msg("no answer from bootstrap DNS server") - return - } - for _, a := range r.Answer { - ip := bootstrapIP(a) - if ip == "" { + // If this is an ipv6, and ipv6 is not available, don't use it as bootstrap ip. + if !ctrldnet.SupportsIPv6() && ctrldnet.IsIPv6(ip) { continue } - - // Storing the ip to uc.bootstrapIPs list, so it can be selected later - // when retrying failed request due to network stack changed. - uc.bootstrapIPs = append(uc.bootstrapIPs, ip) - if uc.BootstrapIP == "" { - // Remember what's the current IP in bootstrap IPs list, - // so we can select next one upon re-bootstrapping. - uc.nextBootstrapIP.Add(1) - - // If this is an ipv6, and ipv6 is not available, don't use it as bootstrap ip. - if !ctrldnet.SupportsIPv6() && ctrldnet.IsIPv6(ip) { - continue - } - uc.BootstrapIP = ip - } + uc.BootstrapIP = ip } } - // Find all A, AAAA records of the upstream. - for _, dnsType := range []uint16{dns.TypeAAAA, dns.TypeA} { - do(dnsType) - } ProxyLog.Debug().Msgf("Bootstrap IPs: %v", uc.bootstrapIPs) } diff --git a/internal/controld/config.go b/internal/controld/config.go index 7092d51..48efef9 100644 --- a/internal/controld/config.go +++ b/internal/controld/config.go @@ -8,11 +8,8 @@ import ( "fmt" "net" "net/http" - "sync" "time" - "github.com/miekg/dns" - "github.com/Control-D-Inc/ctrld" "github.com/Control-D-Inc/ctrld/internal/certs" ctrldnet "github.com/Control-D-Inc/ctrld/internal/net" @@ -25,11 +22,6 @@ const ( InvalidConfigCode = 40401 ) -var ( - resolveAPIDomainOnce sync.Once - apiDomainIP string -) - // ResolverConfig represents Control D resolver data. type ResolverConfig struct { DOH string `json:"doh"` @@ -71,51 +63,19 @@ func FetchResolverConfig(uid string) (*ResolverConfig, error) { req.Header.Add("Content-Type", "application/json") transport := http.DefaultTransport.(*http.Transport).Clone() transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { - // We experiment hanging in TLS handshake when connecting to ControlD API - // with ipv6. So prefer ipv4 if available. - proto := "tcp6" - if ctrldnet.SupportsIPv4() { - proto = "tcp4" + ips := ctrld.LookupIP(apiDomain) + if len(ips) == 0 { + ctrld.ProxyLog.Warn().Msgf("No IPs found for %s, connecting to %s", apiDomain, addr) + return ctrldnet.Dialer.DialContext(ctx, network, addr) } - resolveAPIDomainOnce.Do(func() { - r, err := ctrld.NewResolver(&ctrld.UpstreamConfig{Type: ctrld.ResolverTypeOS}) - if err != nil { - return - } - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - - msg := new(dns.Msg) - dnsType := dns.TypeAAAA - if proto == "tcp4" { - dnsType = dns.TypeA - } - msg.SetQuestion(apiDomain+".", dnsType) - msg.RecursionDesired = true - answer, err := r.Resolve(ctx, msg) - if err != nil { - return - } - if answer.Rcode != dns.RcodeSuccess || len(answer.Answer) == 0 { - return - } - for _, record := range answer.Answer { - switch ar := record.(type) { - case *dns.A: - apiDomainIP = ar.A.String() - return - case *dns.AAAA: - apiDomainIP = ar.AAAA.String() - return - } - } - }) - if apiDomainIP != "" { - if _, port, _ := net.SplitHostPort(addr); port != "" { - return ctrldnet.Dialer.DialContext(ctx, proto, net.JoinHostPort(apiDomainIP, port)) - } + ctrld.ProxyLog.Debug().Msgf("API IPs: %v", ips) + _, port, _ := net.SplitHostPort(addr) + addrs := make([]string, len(ips)) + for i := range ips { + addrs[i] = net.JoinHostPort(ips[i], port) } - return ctrldnet.Dialer.DialContext(ctx, proto, addr) + d := &ctrldnet.ParallelDialer{} + return d.DialContext(ctx, network, addrs) } if router.Name() == router.DDWrt { diff --git a/internal/controld/config_test.go b/internal/controld/config_test.go index cd6ea06..13d937a 100644 --- a/internal/controld/config_test.go +++ b/internal/controld/config_test.go @@ -9,8 +9,6 @@ import ( "github.com/stretchr/testify/require" ) -const utilityURL = "https://api.controld.com/utility" - func TestFetchResolverConfig(t *testing.T) { tests := []struct { name string diff --git a/internal/net/net.go b/internal/net/net.go index 4e71206..e64a908 100644 --- a/internal/net/net.go +++ b/internal/net/net.go @@ -2,6 +2,7 @@ package net import ( "context" + "errors" "net" "sync" "sync/atomic" @@ -37,7 +38,6 @@ var probeStackDialer = &net.Dialer{ var ( stackOnce atomic.Pointer[sync.Once] - ipv4Enabled bool ipv6Enabled bool canListenIPv6Local bool hasNetworkUp bool @@ -75,7 +75,6 @@ func probeStack() { b.BackOff(context.Background(), err) } } - ipv4Enabled = supportIPv4() ipv6Enabled = supportIPv6(context.Background()) canListenIPv6Local = supportListenIPv6Local() } @@ -85,11 +84,6 @@ func Up() bool { return hasNetworkUp } -func SupportsIPv4() bool { - stackOnce.Load().Do(probeStack) - return ipv4Enabled -} - func SupportsIPv6() bool { stackOnce.Load().Do(probeStack) return ipv6Enabled @@ -112,3 +106,47 @@ func IsIPv6(ip string) bool { parsedIP := net.ParseIP(ip) return parsedIP != nil && parsedIP.To4() == nil && parsedIP.To16() != nil } + +type parallelDialerResult struct { + conn net.Conn + err error +} + +type ParallelDialer struct { + net.Dialer +} + +func (d *ParallelDialer) DialContext(ctx context.Context, network string, addrs []string) (net.Conn, error) { + if len(addrs) == 0 { + return nil, errors.New("empty addresses") + } + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + ch := make(chan *parallelDialerResult, len(addrs)) + var wg sync.WaitGroup + wg.Add(len(addrs)) + go func() { + wg.Wait() + close(ch) + }() + + for _, addr := range addrs { + go func(addr string) { + defer wg.Done() + conn, err := d.Dialer.DialContext(ctx, network, addr) + ch <- ¶llelDialerResult{conn: conn, err: err} + }(addr) + } + + errs := make([]error, 0, len(addrs)) + for res := range ch { + if res.err == nil { + cancel() + return res.conn, res.err + } + errs = append(errs, res.err) + } + + return nil, errors.Join(errs...) +} diff --git a/resolver.go b/resolver.go index 77517e6..a1b8efa 100644 --- a/resolver.go +++ b/resolver.go @@ -6,6 +6,7 @@ import ( "fmt" "net" "sync" + "time" "github.com/miekg/dns" ) @@ -79,7 +80,7 @@ func (o *osResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, error for _, server := range o.nameservers { go func(server string) { defer wg.Done() - answer, _, err := dnsClient.ExchangeContext(ctx, msg, server) + answer, _, err := dnsClient.ExchangeContext(ctx, msg.Copy(), server) ch <- &osResolverResult{answer: answer, err: err} }(server) } @@ -122,3 +123,62 @@ func (r *legacyResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, e answer, _, err := dnsClient.ExchangeContext(ctx, msg, r.endpoint) return answer, err } + +// LookupIP looks up host using OS resolver. +// It returns a slice of that host's IPv4 and IPv6 addresses. +func LookupIP(domain string) []string { + return lookupIP(domain, -1, true) +} + +func lookupIP(domain string, timeout int, withBootstrapDNS bool) (ips []string) { + resolver := &osResolver{nameservers: availableNameservers()} + if withBootstrapDNS { + resolver.nameservers = append([]string{net.JoinHostPort(bootstrapDNS, "53")}, resolver.nameservers...) + } + ProxyLog.Debug().Msgf("Resolving %q using bootstrap DNS %q", domain, resolver.nameservers) + timeoutMs := 2000 + if timeout > 0 && timeout < timeoutMs { + timeoutMs = timeoutMs + } + ipFromRecord := func(record dns.RR) string { + switch ar := record.(type) { + case *dns.A: + return ar.A.String() + case *dns.AAAA: + return ar.AAAA.String() + } + return "" + } + + lookup := func(dnsType uint16) { + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutMs)*time.Millisecond) + defer cancel() + m := new(dns.Msg) + m.SetQuestion(domain+".", dnsType) + m.RecursionDesired = true + + r, err := resolver.Resolve(ctx, m) + if err != nil { + ProxyLog.Error().Err(err).Msgf("could not lookup %q record for domain %q", dns.TypeToString[dnsType], domain) + return + } + if r.Rcode != dns.RcodeSuccess { + ProxyLog.Error().Msgf("could not resolve domain %q, return code: %s", domain, dns.RcodeToString[r.Rcode]) + return + } + if len(r.Answer) == 0 { + ProxyLog.Error().Msg("no answer from OS resolver") + return + } + for _, a := range r.Answer { + if ip := ipFromRecord(a); ip != "" { + ips = append(ips, ip) + } + } + } + // Find all A, AAAA records of the domain. + for _, dnsType := range []uint16{dns.TypeAAAA, dns.TypeA} { + lookup(dnsType) + } + return ips +} From 0645a738ad8cb3fe1564a07f34d088044552108e Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 20 Apr 2023 23:16:20 +0700 Subject: [PATCH 14/55] all: add router client info detection This commit add the ability for ctrld to gather client information, including mac/ip/hostname, and send to Control-D server through a config per upstream. - Add send_client_info upstream config. - Read/Watch dnsmasq leases files on supported platforms. - Add corresponding client info to DoH query header All of these only apply for Control-D upstream, though. --- client_info.go | 11 +++++ cmd/ctrld/dns_proxy.go | 28 ++++++++++- cmd/ctrld/dns_proxy_test.go | 36 ++++++++++++++ cmd/ctrld/prog.go | 3 ++ config.go | 54 ++++++++++++++++++--- config_internal_test.go | 26 ++++++++++ config_test.go | 6 ++- doh.go | 32 ++++++++++++- internal/router/client_info.go | 88 ++++++++++++++++++++++++++++++++++ internal/router/ddwrt.go | 11 +++-- internal/router/dnsmasq.go | 37 ++++++++++++-- internal/router/merlin.go | 6 ++- internal/router/merlin_test.go | 2 +- internal/router/openwrt.go | 4 ++ internal/router/router.go | 42 ++++++++++++---- internal/router/ubios.go | 4 ++ testhelper/config.go | 7 +++ 17 files changed, 370 insertions(+), 27 deletions(-) create mode 100644 client_info.go create mode 100644 internal/router/client_info.go diff --git a/client_info.go b/client_info.go new file mode 100644 index 0000000..d0d993a --- /dev/null +++ b/client_info.go @@ -0,0 +1,11 @@ +package ctrld + +// ClientInfoCtxKey is the context key to store client info. +type ClientInfoCtxKey struct{} + +// ClientInfo represents ctrld's clients information. +type ClientInfo struct { + Mac string + IP string + Hostname string +} diff --git a/cmd/ctrld/dns_proxy.go b/cmd/ctrld/dns_proxy.go index 757c7ed..a76f398 100644 --- a/cmd/ctrld/dns_proxy.go +++ b/cmd/ctrld/dns_proxy.go @@ -20,7 +20,13 @@ import ( "github.com/Control-D-Inc/ctrld/internal/router" ) -const staleTTL = 60 * time.Second +const ( + staleTTL = 60 * time.Second + // EDNS0_OPTION_MAC is dnsmasq EDNS0 code for adding mac option. + // https://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=blob;f=src/dns-protocol.h;h=76ac66a8c28317e9c121a74ab5fd0e20f6237dc8;hb=HEAD#l81 + // This is also dns.EDNS0LOCALSTART, but define our own constant here for clarification. + EDNS0_OPTION_MAC = 0xFDE9 +) var osUpstreamConfig = &ctrld.UpstreamConfig{ Name: "OS resolver", @@ -230,6 +236,12 @@ func (p *prog) proxy(ctx context.Context, upstreams []string, failoverRcodes []i return dnsResolver.Resolve(resolveCtx, msg) } resolve := func(n int, upstreamConfig *ctrld.UpstreamConfig, msg *dns.Msg) *dns.Msg { + if upstreamConfig.UpstreamSendClientInfo() { + ci := router.GetClientInfoByMac(macFromMsg(msg)) + if ci != nil { + ctx = context.WithValue(ctx, ctrld.ClientInfoCtxKey{}, ci) + } + } answer, err := resolve1(n, upstreamConfig, msg) if err != nil { ctrld.Log(ctx, mainLog.Debug().Err(err), "could not resolve query on first attempt, retrying...") @@ -386,3 +398,17 @@ func dnsListenAddress(lc *ctrld.ListenerConfig) string { } return net.JoinHostPort(lc.IP, strconv.Itoa(lc.Port)) } + +func macFromMsg(msg *dns.Msg) string { + if opt := msg.IsEdns0(); opt != nil { + for _, s := range opt.Option { + switch e := s.(type) { + case *dns.EDNS0_LOCAL: + if e.Code == EDNS0_OPTION_MAC { + return net.HardwareAddr(e.Data).String() + } + } + } + } + return "" +} diff --git a/cmd/ctrld/dns_proxy_test.go b/cmd/ctrld/dns_proxy_test.go index 6d64346..c9ff9d9 100644 --- a/cmd/ctrld/dns_proxy_test.go +++ b/cmd/ctrld/dns_proxy_test.go @@ -155,3 +155,39 @@ func TestCache(t *testing.T) { assert.Equal(t, answer1.Rcode, got1.Rcode) assert.Equal(t, answer2.Rcode, got2.Rcode) } + +func Test_macFromMsg(t *testing.T) { + tests := []struct { + name string + mac string + wantMac bool + }{ + {"has mac", "4c:20:b8:ab:87:1b", true}, + {"no mac", "4c:20:b8:ab:87:1b", false}, + } + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + hw, err := net.ParseMAC(tc.mac) + if err != nil { + t.Fatal(err) + } + m := new(dns.Msg) + m.SetQuestion(selfCheckFQDN+".", dns.TypeA) + o := &dns.OPT{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeOPT}} + if tc.wantMac { + ec1 := &dns.EDNS0_LOCAL{Code: EDNS0_OPTION_MAC, Data: hw} + o.Option = append(o.Option, ec1) + } + m.Extra = append(m.Extra, o) + got := macFromMsg(m) + if tc.wantMac && got != tc.mac { + t.Errorf("mismatch, want: %q, got: %q", tc.mac, got) + } + if !tc.wantMac && got != "" { + t.Errorf("unexpected mac: %q", got) + } + }) + } +} diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index 08251ef..549d9ff 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -139,6 +139,9 @@ func (p *prog) Stop(s service.Service) error { return err } p.preStop() + if err := router.Stop(); err != nil { + mainLog.Warn().Err(err).Msg("problem occurred while stopping router") + } mainLog.Info().Msg("Service stopped") close(p.stopCh) return nil diff --git a/config.go b/config.go index 7afaac9..c77d73c 100644 --- a/config.go +++ b/config.go @@ -80,6 +80,17 @@ type Config struct { Upstream map[string]*UpstreamConfig `mapstructure:"upstream" toml:"upstream" validate:"min=1,dive"` } +// HasUpstreamSendClientInfo reports whether the config has any upstream +// is configured to send client info to Control D DNS server. +func (c *Config) HasUpstreamSendClientInfo() bool { + for _, uc := range c.Upstream { + if uc.UpstreamSendClientInfo() { + return true + } + } + return false +} + // ServiceConfig specifies the general ctrld config. type ServiceConfig struct { LogLevel string `mapstructure:"log_level" toml:"log_level,omitempty"` @@ -101,12 +112,15 @@ type NetworkConfig struct { // UpstreamConfig specifies configuration for upstreams that ctrld will forward requests to. type UpstreamConfig struct { - Name string `mapstructure:"name" toml:"name,omitempty"` - Type string `mapstructure:"type" toml:"type,omitempty" validate:"oneof=doh doh3 dot doq os legacy"` - Endpoint string `mapstructure:"endpoint" toml:"endpoint,omitempty" validate:"required_unless=Type os"` - BootstrapIP string `mapstructure:"bootstrap_ip" toml:"bootstrap_ip,omitempty"` - Domain string `mapstructure:"-" toml:"-"` - Timeout int `mapstructure:"timeout" toml:"timeout,omitempty" validate:"gte=0"` + Name string `mapstructure:"name" toml:"name,omitempty"` + Type string `mapstructure:"type" toml:"type,omitempty" validate:"oneof=doh doh3 dot doq os legacy"` + Endpoint string `mapstructure:"endpoint" toml:"endpoint,omitempty" validate:"required_unless=Type os"` + BootstrapIP string `mapstructure:"bootstrap_ip" toml:"bootstrap_ip,omitempty"` + Domain string `mapstructure:"-" toml:"-"` + Timeout int `mapstructure:"timeout" toml:"timeout,omitempty" validate:"gte=0"` + // The caller should not access this field directly. + // Use UpstreamSendClientInfo instead. + SendClientInfo *bool `mapstructure:"send_client_info" toml:"send_client_info,omitempty"` transport *http.Transport `mapstructure:"-" toml:"-"` http3RoundTripper http.RoundTripper `mapstructure:"-" toml:"-"` certPool *x509.CertPool `mapstructure:"-" toml:"-"` @@ -163,6 +177,34 @@ func (uc *UpstreamConfig) Init() { } } +// UpstreamSendClientInfo reports whether the upstream is +// configured to send client info to Control D DNS server. +// +// Client info includes: +// - MAC +// - Lan IP +// - Hostname +func (uc *UpstreamConfig) UpstreamSendClientInfo() bool { + if uc.SendClientInfo != nil && !(*uc.SendClientInfo) { + return false + } + if uc.SendClientInfo == nil { + return true + } + switch uc.Type { + case ResolverTypeDOH, ResolverTypeDOH3: + if u, err := url.Parse(uc.Endpoint); err == nil { + domain := u.Hostname() + for _, parent := range []string{"controld.com", "controld.net"} { + if dns.IsSubDomain(parent, domain) { + return true + } + } + } + } + return false +} + // SetCertPool sets the system cert pool used for TLS connections. func (uc *UpstreamConfig) SetCertPool(cp *x509.CertPool) { uc.certPool = cp diff --git a/config_internal_test.go b/config_internal_test.go index a470cf8..4ec872a 100644 --- a/config_internal_test.go +++ b/config_internal_test.go @@ -147,6 +147,28 @@ func TestUpstreamConfig_Init(t *testing.T) { Timeout: 0, }, }, + { + "doh+doh3 with send client info set", + &UpstreamConfig{ + Name: "doh", + Type: "doh", + Endpoint: "https://example.com?k=v", + BootstrapIP: "", + Domain: "", + Timeout: 0, + SendClientInfo: ptrBool(false), + }, + &UpstreamConfig{ + Name: "doh", + Type: "doh", + Endpoint: "https://example.com?k=v", + BootstrapIP: "", + Domain: "example.com", + Timeout: 0, + SendClientInfo: ptrBool(false), + u: u2, + }, + }, } for _, tc := range tests { @@ -158,3 +180,7 @@ func TestUpstreamConfig_Init(t *testing.T) { }) } } + +func ptrBool(b bool) *bool { + return &b +} diff --git a/config_test.go b/config_test.go index e2d75c0..90cd81a 100644 --- a/config_test.go +++ b/config_test.go @@ -24,10 +24,12 @@ func TestLoadConfig(t *testing.T) { assert.Contains(t, cfg.Network, "0") assert.Contains(t, cfg.Network, "1") - assert.Len(t, cfg.Upstream, 3) + assert.Len(t, cfg.Upstream, 4) assert.Contains(t, cfg.Upstream, "0") assert.Contains(t, cfg.Upstream, "1") assert.Contains(t, cfg.Upstream, "2") + assert.Contains(t, cfg.Upstream, "3") + assert.NotNil(t, cfg.Upstream["3"].SendClientInfo) assert.Len(t, cfg.Listener, 2) assert.Contains(t, cfg.Listener, "0") @@ -42,6 +44,8 @@ func TestLoadConfig(t *testing.T) { assert.Len(t, cfg.Listener["0"].Policy.Rules, 2) assert.Contains(t, cfg.Listener["0"].Policy.Rules[0], "*.ru") assert.Contains(t, cfg.Listener["0"].Policy.Rules[1], "*.local.host") + + assert.True(t, cfg.HasUpstreamSendClientInfo()) } func TestLoadDefaultConfig(t *testing.T) { diff --git a/doh.go b/doh.go index 4fd4bd6..1cd9717 100644 --- a/doh.go +++ b/doh.go @@ -12,12 +12,22 @@ import ( "github.com/miekg/dns" ) +const ( + DoHMacHeader = "Dns-Mac" + DoHIPHeader = "Dns-IP" + DoHHostHeader = "Dns-Host" + + headerContentTypeValue = "application/dns-message" + headerAcceptValue = "application/dns-message" +) + func newDohResolver(uc *UpstreamConfig) *dohResolver { r := &dohResolver{ endpoint: uc.u, isDoH3: uc.Type == ResolverTypeDOH3, transport: uc.transport, http3RoundTripper: uc.http3RoundTripper, + sendClientInfo: uc.UpstreamSendClientInfo(), } return r } @@ -27,6 +37,7 @@ type dohResolver struct { isDoH3 bool transport *http.Transport http3RoundTripper http.RoundTripper + sendClientInfo bool } func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) { @@ -45,8 +56,7 @@ func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro if err != nil { return nil, fmt.Errorf("could not create request: %w", err) } - req.Header.Set("Content-Type", "application/dns-message") - req.Header.Set("Accept", "application/dns-message") + addHeader(ctx, req, r.sendClientInfo) c := http.Client{Transport: r.transport} if r.isDoH3 { @@ -78,3 +88,21 @@ func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro answer := new(dns.Msg) return answer, answer.Unpack(buf) } + +func addHeader(ctx context.Context, req *http.Request, sendClientInfo bool) { + req.Header.Set("Content-Type", headerContentTypeValue) + req.Header.Set("Accept", headerAcceptValue) + if sendClientInfo { + if ci, ok := ctx.Value(ClientInfoCtxKey{}).(*ClientInfo); ok && ci != nil { + if ci.Mac != "" { + req.Header.Set(DoHMacHeader, ci.Mac) + } + if ci.IP != "" { + req.Header.Set(DoHIPHeader, ci.IP) + } + if ci.Hostname != "" { + req.Header.Set(DoHHostHeader, ci.Hostname) + } + } + } +} diff --git a/internal/router/client_info.go b/internal/router/client_info.go new file mode 100644 index 0000000..05fafa2 --- /dev/null +++ b/internal/router/client_info.go @@ -0,0 +1,88 @@ +package router + +import ( + "bytes" + "log" + "os" + "time" + + "github.com/fsnotify/fsnotify" + "tailscale.com/util/lineread" + + "github.com/Control-D-Inc/ctrld" +) + +var clientInfoFiles = []string{ + "/tmp/dnsmasq.leases", // ddwrt + "/tmp/dhcp.leases", // openwrt + "/var/lib/misc/dnsmasq.leases", // merlin + "/mnt/data/udapi-config/dnsmasq.lease", // UDM Pro + "/data/udapi-config/dnsmasq.lease", // UDR +} + +func (r *router) watchClientInfoTable() { + if r.watcher == nil { + return + } + timer := time.NewTicker(time.Minute * 5) + for { + select { + case <-timer.C: + for _, name := range r.watcher.WatchList() { + _ = readClientInfoFile(name) + } + case event, ok := <-r.watcher.Events: + if !ok { + return + } + if event.Has(fsnotify.Write) { + if err := readClientInfoFile(event.Name); err != nil && !os.IsNotExist(err) { + log.Println("could not read client info file:", err) + } + } + case err, ok := <-r.watcher.Errors: + if !ok { + return + } + log.Println("error:", err) + } + } +} + +func Stop() error { + if Name() == "" { + return nil + } + r := routerPlatform.Load() + if r.watcher != nil { + if err := r.watcher.Close(); err != nil { + return err + } + } + return nil +} + +func GetClientInfoByMac(mac string) *ctrld.ClientInfo { + if mac == "" { + return nil + } + _ = Name() + r := routerPlatform.Load() + val, ok := r.mac.Load(mac) + if !ok { + return nil + } + return val.(*ctrld.ClientInfo) +} + +func readClientInfoFile(name string) error { + r := routerPlatform.Load() + return lineread.File(name, func(line []byte) error { + fields := bytes.Fields(line) + mac := string(fields[1]) + ip := string(fields[2]) + hostname := string(fields[3]) + r.mac.Store(mac, &ctrld.ClientInfo{Mac: mac, IP: ip, Hostname: hostname}) + return nil + }) +} diff --git a/internal/router/ddwrt.go b/internal/router/ddwrt.go index 022e82e..ad6e921 100644 --- a/internal/router/ddwrt.go +++ b/internal/router/ddwrt.go @@ -20,9 +20,9 @@ https://wiki.dd-wrt.com/wiki/index.php/Journalling_Flash_File_System `) var nvramKeys = map[string]string{ - "dns_dnsmasq": "1", // Make dnsmasq running but disable DNS ability, ctrld will replace it. - "dnsmasq_options": dnsMasqConfigContent, // Configuration of dnsmasq set by ctrld. - "dns_crypt": "0", // Disable DNSCrypt. + "dns_dnsmasq": "1", // Make dnsmasq running but disable DNS ability, ctrld will replace it. + "dnsmasq_options": "", // Configuration of dnsmasq set by ctrld, filled by setupDDWrt. + "dns_crypt": "0", // Disable DNSCrypt. } func setupDDWrt() error { @@ -31,6 +31,11 @@ func setupDDWrt() error { return nil } + data, err := dnsMasqConf() + if err != nil { + return err + } + nvramKeys["dnsmasq_options"] = data // Backup current value, store ctrld's configs. for key, value := range nvramKeys { old, err := nvram("get", key) diff --git a/internal/router/dnsmasq.go b/internal/router/dnsmasq.go index d90854a..009bf27 100644 --- a/internal/router/dnsmasq.go +++ b/internal/router/dnsmasq.go @@ -1,14 +1,22 @@ package router -const dnsMasqConfigContent = `# GENERATED BY ctrld - DO NOT MODIFY +import ( + "strings" + "text/template" +) + +const dnsMasqConfigContentTmpl = `# GENERATED BY ctrld - DO NOT MODIFY no-resolv server=127.0.0.1#5354 +{{- if .SendClientInfo}} +add-mac +{{- end}} ` const merlinDNSMasqPostConfPath = "/jffs/scripts/dnsmasq.postconf" const merlinDNSMasqPostConfMarker = `# GENERATED BY ctrld - EOF` -const merlinDNSMasqPostConf = `# GENERATED BY ctrld - DO NOT MODIFY +const merlinDNSMasqPostConfTmpl = `# GENERATED BY ctrld - DO NOT MODIFY #!/bin/sh @@ -20,7 +28,9 @@ if [ -n "$pid" ] && [ -f "/proc/${pid}/cmdline" ]; then pc_delete "servers-file" "$config_file" # no WAN DNS settings pc_append "no-resolv" "$config_file" # do not read /etc/resolv.conf pc_append "server=127.0.0.1#5354" "$config_file" # use ctrld as upstream - + {{- if .SendClientInfo}} + pc_append "add-mac" "$config_file" # add client mac + {{- end}} # For John fork pc_delete "resolv-file" "$config_file" # no WAN DNS settings @@ -32,3 +42,24 @@ if [ -n "$pid" ] && [ -f "/proc/${pid}/cmdline" ]; then exit 0 fi ` + +func dnsMasqConf() (string, error) { + var sb strings.Builder + var tmplText string + switch Name() { + case DDWrt, OpenWrt, Ubios: + tmplText = dnsMasqConfigContentTmpl + case Merlin: + tmplText = merlinDNSMasqPostConfTmpl + } + tmpl := template.Must(template.New("").Parse(tmplText)) + var to = &struct { + SendClientInfo bool + }{ + routerPlatform.Load().sendClientInfo, + } + if err := tmpl.Execute(&sb, to); err != nil { + return "", err + } + return sb.String(), nil +} diff --git a/internal/router/merlin.go b/internal/router/merlin.go index 0048d17..b2386bf 100644 --- a/internal/router/merlin.go +++ b/internal/router/merlin.go @@ -19,6 +19,10 @@ func setupMerlin() error { return err } + merlinDNSMasqPostConf, err := dnsMasqConf() + if err != nil { + return err + } data := strings.Join([]string{ merlinDNSMasqPostConf, "\n", @@ -38,7 +42,7 @@ func setupMerlin() error { } func cleanupMerlin() error { - buf, err := os.ReadFile(merlinDNSMasqPostConf) + buf, err := os.ReadFile(merlinDNSMasqPostConfPath) if err != nil && !os.IsNotExist(err) { return err } diff --git a/internal/router/merlin_test.go b/internal/router/merlin_test.go index 2a3c241..e1715af 100644 --- a/internal/router/merlin_test.go +++ b/internal/router/merlin_test.go @@ -9,7 +9,7 @@ import ( func Test_merlinParsePostConf(t *testing.T) { origContent := "# foo" data := strings.Join([]string{ - merlinDNSMasqPostConf, + merlinDNSMasqPostConfTmpl, "\n", merlinDNSMasqPostConfMarker, "\n", diff --git a/internal/router/openwrt.go b/internal/router/openwrt.go index 177be36..97f4628 100644 --- a/internal/router/openwrt.go +++ b/internal/router/openwrt.go @@ -19,6 +19,10 @@ func setupOpenWrt() error { return err } // Disable dnsmasq as DNS server. + dnsMasqConfigContent, err := dnsMasqConf() + if err != nil { + return err + } if err := os.WriteFile(openwrtDNSMasqConfigPath, []byte(dnsMasqConfigContent), 0600); err != nil { return err } diff --git a/internal/router/router.go b/internal/router/router.go index 13ec295..bc628ad 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -5,8 +5,10 @@ import ( "errors" "os" "os/exec" + "sync" "sync/atomic" + "github.com/fsnotify/fsnotify" "github.com/kardianos/service" "github.com/Control-D-Inc/ctrld" @@ -25,7 +27,10 @@ var ErrNotSupported = errors.New("unsupported platform") var routerPlatform atomic.Pointer[router] type router struct { - name string + name string + sendClientInfo bool + mac sync.Map + watcher *fsnotify.Watcher } // SupportedPlatforms return all platforms that can be configured to run with ctrld. @@ -33,18 +38,37 @@ func SupportedPlatforms() []string { return []string{DDWrt, Merlin, OpenWrt, Ubios} } +var configureFunc = map[string]func() error{ + DDWrt: setupDDWrt, + Merlin: setupMerlin, + OpenWrt: setupOpenWrt, + Ubios: setupUbiOS, +} + // Configure configures things for running ctrld on the router. func Configure(c *ctrld.Config) error { name := Name() switch name { - case DDWrt: - return setupDDWrt() - case Merlin: - return setupMerlin() - case OpenWrt: - return setupOpenWrt() - case Ubios: - return setupUbiOS() + case DDWrt, Merlin, OpenWrt, Ubios: + if c.HasUpstreamSendClientInfo() { + r := routerPlatform.Load() + r.sendClientInfo = true + watcher, err := fsnotify.NewWatcher() + if err != nil { + return err + } + r.watcher = watcher + go r.watchClientInfoTable() + for _, file := range clientInfoFiles { + _ = readClientInfoFile(file) + _ = r.watcher.Add(file) + } + } + configure := configureFunc[name] + if err := configure(); err != nil { + return err + } + return nil default: return ErrNotSupported } diff --git a/internal/router/ubios.go b/internal/router/ubios.go index 8affb33..80fe04b 100644 --- a/internal/router/ubios.go +++ b/internal/router/ubios.go @@ -12,6 +12,10 @@ const ( func setupUbiOS() error { // Disable dnsmasq as DNS server. + dnsMasqConfigContent, err := dnsMasqConf() + if err != nil { + return err + } if err := os.WriteFile(ubiosDNSMasqConfigPath, []byte(dnsMasqConfigContent), 0600); err != nil { return err } diff --git a/testhelper/config.go b/testhelper/config.go index 6d646be..0b739f0 100644 --- a/testhelper/config.go +++ b/testhelper/config.go @@ -50,6 +50,13 @@ type = "legacy" endpoint = "8.8.8.8" timeout = 5 +[upstream.3] +name = "DOH with client info" +type = "doh" +endpoint = "https://dns.controld.com/client_info_upstream/main-device" +timeout = 5 +send_client_info = false + [listener.0] ip = "127.0.0.1" port = 53 From f73cbde7a5aca891f962f85576bbfebeee6798a9 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 24 Apr 2023 23:50:14 +0700 Subject: [PATCH 15/55] Update HTTP request headers --- doh.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/doh.go b/doh.go index 1cd9717..e2e6586 100644 --- a/doh.go +++ b/doh.go @@ -13,12 +13,11 @@ import ( ) const ( - DoHMacHeader = "Dns-Mac" - DoHIPHeader = "Dns-IP" - DoHHostHeader = "Dns-Host" + DoHMacHeader = "x-cd-mac" + DoHIPHeader = "x-cd-ip" + DoHHostHeader = "x-cd-host" - headerContentTypeValue = "application/dns-message" - headerAcceptValue = "application/dns-message" + headerApplicationDNS = "application/dns-message" ) func newDohResolver(uc *UpstreamConfig) *dohResolver { @@ -90,8 +89,8 @@ func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro } func addHeader(ctx context.Context, req *http.Request, sendClientInfo bool) { - req.Header.Set("Content-Type", headerContentTypeValue) - req.Header.Set("Accept", headerAcceptValue) + req.Header.Set("Content-Type", headerApplicationDNS) + req.Header.Set("Accept", headerApplicationDNS) if sendClientInfo { if ci, ok := ctx.Value(ClientInfoCtxKey{}).(*ClientInfo); ok && ci != nil { if ci.Mac != "" { From 0af7f64bcacfc5dac02e86b4e9abfe2e71553c67 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 25 Apr 2023 01:36:51 +0700 Subject: [PATCH 16/55] all: use parallel dialer for bootstrapping ip So we don't have to depend on network probing for checking ipv4/ipv6 enabled, making ctrld working more stably. --- cmd/ctrld/os_linux.go | 2 +- cmd/ctrld/prog.go | 2 +- config.go | 87 +++++++++++---------------------------- config_internal_test.go | 4 +- config_quic.go | 91 +++++++++++++++++++++++++++++++++++------ internal/net/net.go | 15 +------ resolver.go | 2 +- 7 files changed, 108 insertions(+), 95 deletions(-) diff --git a/cmd/ctrld/os_linux.go b/cmd/ctrld/os_linux.go index 839d99d..307ee3a 100644 --- a/cmd/ctrld/os_linux.go +++ b/cmd/ctrld/os_linux.go @@ -112,7 +112,7 @@ func resetDNS(iface *net.Interface) (err error) { } // TODO(cuonglm): handle DHCPv6 properly. - if ctrldnet.SupportsIPv6() { + if ctrldnet.IPv6Available(ctx) { c := client6.NewClient() conversation, err := c.Exchange(iface.Name) if err != nil { diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index 549d9ff..9aba028 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -74,7 +74,7 @@ func (p *prog) run() { uc.Init() if uc.BootstrapIP == "" { uc.SetupBootstrapIP() - mainLog.Info().Str("bootstrap_ip", uc.BootstrapIP).Msgf("Setting bootstrap IP for upstream.%s", n) + mainLog.Info().Msgf("Bootstrap IPs for upstream.%s: %q", n, uc.BootstrapIPs()) } else { mainLog.Info().Str("bootstrap_ip", uc.BootstrapIP).Msgf("Using bootstrap IP for upstream.%s", n) } diff --git a/config.go b/config.go index c77d73c..7e7bccb 100644 --- a/config.go +++ b/config.go @@ -205,6 +205,10 @@ func (uc *UpstreamConfig) UpstreamSendClientInfo() bool { return false } +func (uc *UpstreamConfig) BootstrapIPs() []string { + return uc.bootstrapIPs +} + // SetCertPool sets the system cert pool used for TLS connections. func (uc *UpstreamConfig) SetCertPool(cp *x509.CertPool) { uc.certPool = cp @@ -220,19 +224,6 @@ func (uc *UpstreamConfig) SetupBootstrapIP() { // The first usable IP will be used as bootstrap IP of the upstream. func (uc *UpstreamConfig) setupBootstrapIP(withBootstrapDNS bool) { uc.bootstrapIPs = lookupIP(uc.Domain, uc.Timeout, withBootstrapDNS) - for _, ip := range uc.bootstrapIPs { - if uc.BootstrapIP == "" { - // Remember what's the current IP in bootstrap IPs list, - // so we can select next one upon re-bootstrapping. - uc.nextBootstrapIP.Add(1) - - // If this is an ipv6, and ipv6 is not available, don't use it as bootstrap ip. - if !ctrldnet.SupportsIPv6() && ctrldnet.IsIPv6(ip) { - continue - } - uc.BootstrapIP = ip - } - } ProxyLog.Debug().Msgf("Bootstrap IPs: %v", uc.bootstrapIPs) } @@ -245,32 +236,7 @@ func (uc *UpstreamConfig) ReBootstrap() { } _, _, _ = uc.g.Do("ReBootstrap", func() (any, error) { ProxyLog.Debug().Msg("re-bootstrapping upstream ip") - n := uint32(len(uc.bootstrapIPs)) - if n == 0 { - uc.SetupBootstrapIP() - uc.setupTransportWithoutPingUpstream() - } - - timeoutMs := 1000 - if uc.Timeout > 0 && uc.Timeout < timeoutMs { - timeoutMs = uc.Timeout - } - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutMs)*time.Millisecond) - defer cancel() - - hasIPv6 := ctrldnet.IPv6Available(ctx) - // Only attempt n times, because if there's no usable ip, - // the bootstrap ip will be kept as-is. - for i := uint32(0); i < n; i++ { - // Select the next ip in bootstrap ip list. - next := uc.nextBootstrapIP.Add(1) - ip := uc.bootstrapIPs[(next-1)%n] - if !hasIPv6 && ctrldnet.IsIPv6(ip) { - continue - } - uc.BootstrapIP = ip - break - } + uc.BootstrapIP = "" uc.setupTransportWithoutPingUpstream() return true, nil }) @@ -312,18 +278,26 @@ func (uc *UpstreamConfig) setupDOHTransportWithoutPingUpstream() { } dialerTimeout := time.Duration(dialerTimeoutMs) * time.Millisecond uc.transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { - dialer := &net.Dialer{ - Timeout: dialerTimeout, - KeepAlive: dialerTimeout, - } - // if we have a bootstrap ip set, use it to avoid DNS lookup + _, port, _ := net.SplitHostPort(addr) if uc.BootstrapIP != "" { - if _, port, _ := net.SplitHostPort(addr); port != "" { - addr = net.JoinHostPort(uc.BootstrapIP, port) - } + dialer := net.Dialer{Timeout: dialerTimeout, KeepAlive: dialerTimeout} + addr := net.JoinHostPort(uc.BootstrapIP, port) + Log(ctx, ProxyLog.Debug(), "sending doh request to: %s", addr) + return dialer.DialContext(ctx, network, addr) } - Log(ctx, ProxyLog.Debug(), "sending doh request to: %s", addr) - return dialer.DialContext(ctx, network, addr) + pd := &ctrldnet.ParallelDialer{} + pd.Timeout = dialerTimeout + pd.KeepAlive = dialerTimeout + addrs := make([]string, len(uc.bootstrapIPs)) + for i := range uc.bootstrapIPs { + addrs[i] = net.JoinHostPort(uc.bootstrapIPs[i], port) + } + conn, err := pd.DialContext(ctx, network, addrs) + if err != nil { + return nil, err + } + Log(ctx, ProxyLog.Debug(), "sending doh request to: %s", conn.RemoteAddr()) + return conn, nil } } @@ -374,21 +348,6 @@ func defaultPortFor(typ string) string { return "53" } -func availableNameservers() []string { - nss := nameservers() - n := 0 - for _, ns := range nss { - ip, _, _ := net.SplitHostPort(ns) - // skipping invalid entry or ipv6 nameserver if ipv6 not available. - if ip == "" || (ctrldnet.IsIPv6(ip) && !ctrldnet.SupportsIPv6()) { - continue - } - nss[n] = ns - n++ - } - return nss[:n] -} - // ResolverTypeFromEndpoint tries guessing the resolver type with a given endpoint // using following rules: // diff --git a/config_internal_test.go b/config_internal_test.go index 4ec872a..4c67826 100644 --- a/config_internal_test.go +++ b/config_internal_test.go @@ -16,8 +16,8 @@ func TestUpstreamConfig_SetupBootstrapIP(t *testing.T) { } uc.Init() uc.setupBootstrapIP(false) - if uc.BootstrapIP == "" { - t.Log(availableNameservers()) + if len(uc.bootstrapIPs) == 0 { + t.Log(nameservers()) t.Fatal("could not bootstrap ip without bootstrap DNS") } t.Log(uc) diff --git a/config_quic.go b/config_quic.go index c9b641f..4b9f4c9 100644 --- a/config_quic.go +++ b/config_quic.go @@ -5,7 +5,9 @@ package ctrld import ( "context" "crypto/tls" + "errors" "net" + "sync" "github.com/quic-go/quic-go" "github.com/quic-go/quic-go/http3" @@ -20,26 +22,91 @@ func (uc *UpstreamConfig) setupDOH3TransportWithoutPingUpstream() { rt := &http3.RoundTripper{} rt.TLSClientConfig = &tls.Config{RootCAs: uc.certPool} rt.Dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { - host := addr - ProxyLog.Debug().Msgf("debug dial context D0H3 %s - %s", addr, bootstrapDNS) + domain := addr + _, port, _ := net.SplitHostPort(addr) // if we have a bootstrap ip set, use it to avoid DNS lookup if uc.BootstrapIP != "" { - if _, port, _ := net.SplitHostPort(addr); port != "" { - addr = net.JoinHostPort(uc.BootstrapIP, port) - } + addr = net.JoinHostPort(uc.BootstrapIP, port) ProxyLog.Debug().Msgf("sending doh3 request to: %s", addr) + udpConn, err := net.ListenUDP("udp", nil) + if err != nil { + return nil, err + } + remoteAddr, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + return nil, err + } + return quic.DialEarlyContext(ctx, udpConn, remoteAddr, domain, tlsCfg, cfg) } - remoteAddr, err := net.ResolveUDPAddr("udp", addr) + addrs := make([]string, len(uc.bootstrapIPs)) + for i := range uc.bootstrapIPs { + addrs[i] = net.JoinHostPort(uc.bootstrapIPs[i], port) + } + pd := &quicParallelDialer{} + conn, err := pd.Dial(ctx, domain, addrs, tlsCfg, cfg) if err != nil { return nil, err } - - udpConn, err := net.ListenUDP("udp", nil) - if err != nil { - return nil, err - } - return quic.DialEarlyContext(ctx, udpConn, remoteAddr, host, tlsCfg, cfg) + ProxyLog.Debug().Msgf("sending doh3 request to: %s", conn.RemoteAddr()) + return conn, err } uc.http3RoundTripper = rt } + +// Putting the code for quic parallel dialer here: +// +// - quic dialer is different with net.Dialer +// - simplification for quic free version +type parallelDialerResult struct { + conn quic.EarlyConnection + err error +} + +type quicParallelDialer struct{} + +func (d *quicParallelDialer) Dial(ctx context.Context, domain string, addrs []string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { + if len(addrs) == 0 { + return nil, errors.New("empty addresses") + } + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + ch := make(chan *parallelDialerResult, len(addrs)) + var wg sync.WaitGroup + wg.Add(len(addrs)) + go func() { + wg.Wait() + close(ch) + }() + + udpConn, err := net.ListenUDP("udp", nil) + if err != nil { + return nil, err + } + + for _, addr := range addrs { + go func(addr string) { + defer wg.Done() + remoteAddr, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + ch <- ¶llelDialerResult{conn: nil, err: err} + return + } + + conn, err := quic.DialEarlyContext(ctx, udpConn, remoteAddr, domain, tlsCfg, cfg) + ch <- ¶llelDialerResult{conn: conn, err: err} + }(addr) + } + + errs := make([]error, 0, len(addrs)) + for res := range ch { + if res.err == nil { + cancel() + return res.conn, res.err + } + errs = append(errs, res.err) + } + + return nil, errors.Join(errs...) +} diff --git a/internal/net/net.go b/internal/net/net.go index e64a908..1c43bbb 100644 --- a/internal/net/net.go +++ b/internal/net/net.go @@ -13,7 +13,6 @@ import ( const ( controldIPv6Test = "ipv6.controld.io" - controldIPv4Test = "ipv4.controld.io" bootstrapDNS = "76.76.2.0:53" ) @@ -38,7 +37,6 @@ var probeStackDialer = &net.Dialer{ var ( stackOnce atomic.Pointer[sync.Once] - ipv6Enabled bool canListenIPv6Local bool hasNetworkUp bool ) @@ -47,13 +45,8 @@ func init() { stackOnce.Store(new(sync.Once)) } -func supportIPv4() bool { - _, err := probeStackDialer.Dial("tcp4", net.JoinHostPort(controldIPv4Test, "80")) - return err == nil -} - func supportIPv6(ctx context.Context) bool { - _, err := probeStackDialer.DialContext(ctx, "tcp6", net.JoinHostPort(controldIPv6Test, "80")) + _, err := probeStackDialer.DialContext(ctx, "tcp6", net.JoinHostPort(controldIPv6Test, "443")) return err == nil } @@ -75,7 +68,6 @@ func probeStack() { b.BackOff(context.Background(), err) } } - ipv6Enabled = supportIPv6(context.Background()) canListenIPv6Local = supportListenIPv6Local() } @@ -84,11 +76,6 @@ func Up() bool { return hasNetworkUp } -func SupportsIPv6() bool { - stackOnce.Load().Do(probeStack) - return ipv6Enabled -} - func SupportsIPv6ListenLocal() bool { stackOnce.Load().Do(probeStack) return canListenIPv6Local diff --git a/resolver.go b/resolver.go index a1b8efa..084281c 100644 --- a/resolver.go +++ b/resolver.go @@ -131,7 +131,7 @@ func LookupIP(domain string) []string { } func lookupIP(domain string, timeout int, withBootstrapDNS bool) (ips []string) { - resolver := &osResolver{nameservers: availableNameservers()} + resolver := &osResolver{nameservers: nameservers()} if withBootstrapDNS { resolver.nameservers = append([]string{net.JoinHostPort(bootstrapDNS, "53")}, resolver.nameservers...) } From 9df381d3d17b60c25388d3d9434a54681cd7b1ae Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 26 Apr 2023 18:33:04 +0700 Subject: [PATCH 17/55] all: add "version" query param when fetching config --- cmd/ctrld/cli.go | 2 +- internal/controld/config.go | 3 ++- internal/controld/config_test.go | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 5f18933..460c113 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -669,7 +669,7 @@ func processCDFlags() { } logger := mainLog.With().Str("mode", "cd").Logger() logger.Info().Msgf("fetching Controld D configuration from API: %s", cdUID) - resolverConfig, err := controld.FetchResolverConfig(cdUID) + resolverConfig, err := controld.FetchResolverConfig(cdUID, rootCmd.Version) if uer, ok := err.(*controld.UtilityErrorResponse); ok && uer.ErrorField.Code == controld.InvalidConfigCode { s, err := service.New(&prog{}, svcConfig) if err != nil { diff --git a/internal/controld/config.go b/internal/controld/config.go index 48efef9..a77eecd 100644 --- a/internal/controld/config.go +++ b/internal/controld/config.go @@ -51,7 +51,7 @@ type utilityRequest struct { } // FetchResolverConfig fetch Control D config for given uid. -func FetchResolverConfig(uid string) (*ResolverConfig, error) { +func FetchResolverConfig(uid, version string) (*ResolverConfig, error) { body, _ := json.Marshal(utilityRequest{UID: uid}) req, err := http.NewRequest("POST", resolverDataURL, bytes.NewReader(body)) if err != nil { @@ -59,6 +59,7 @@ func FetchResolverConfig(uid string) (*ResolverConfig, error) { } q := req.URL.Query() q.Set("platform", "ctrld") + q.Set("version", version) req.URL.RawQuery = q.Encode() req.Header.Add("Content-Type", "application/json") transport := http.DefaultTransport.(*http.Transport).Clone() diff --git a/internal/controld/config_test.go b/internal/controld/config_test.go index 13d937a..d46cf95 100644 --- a/internal/controld/config_test.go +++ b/internal/controld/config_test.go @@ -22,7 +22,7 @@ func TestFetchResolverConfig(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() - got, err := FetchResolverConfig(tc.uid) + got, err := FetchResolverConfig(tc.uid, "dev-test") require.False(t, (err != nil) != tc.wantErr, err) if !tc.wantErr { assert.NotEmpty(t, got.DOH) From 69319c6b41538f0040c3e354d81629b3d20a7e21 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 26 Apr 2023 18:55:53 +0700 Subject: [PATCH 18/55] all: support custom config from Control-D resolver --- cmd/ctrld/cli.go | 64 +++++++++++++++++++++---------------- internal/controld/config.go | 5 ++- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 460c113..8dd5298 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -162,7 +162,7 @@ func initCLI() { } } - readBase64Config() + readBase64Config(configBase64) processNoConfigFlags(noConfigStart) if err := v.Unmarshal(&cfg); err != nil { log.Fatalf("failed to unmarshal config: %v", err) @@ -607,7 +607,7 @@ func readConfigFile(writeDefaultConfig bool) bool { return false } -func readBase64Config() { +func readBase64Config(configBase64 string) { if configBase64 == "" { return } @@ -701,34 +701,42 @@ func processCDFlags() { return } - logger.Info().Msg("generating ctrld config from Controld-D configuration") - cfg = ctrld.Config{} - cfg.Network = make(map[string]*ctrld.NetworkConfig) - cfg.Network["0"] = &ctrld.NetworkConfig{ - Name: "Network 0", - Cidrs: []string{"0.0.0.0/0"}, - } - cfg.Upstream = make(map[string]*ctrld.UpstreamConfig) - cfg.Upstream["0"] = &ctrld.UpstreamConfig{ - Endpoint: resolverConfig.DOH, - Type: ctrld.ResolverTypeDOH, - Timeout: 5000, - } - rules := make([]ctrld.Rule, 0, len(resolverConfig.Exclude)) - for _, domain := range resolverConfig.Exclude { - rules = append(rules, ctrld.Rule{domain: []string{}}) - } - cfg.Listener = make(map[string]*ctrld.ListenerConfig) - cfg.Listener["0"] = &ctrld.ListenerConfig{ - IP: "127.0.0.1", - Port: 53, - Policy: &ctrld.ListenerPolicyConfig{ - Name: "My Policy", - Rules: rules, - }, + logger.Info().Msg("generating ctrld config from Control-D configuration") + if resolverConfig.Ctrld.CustomConfig != "" { + logger.Info().Msg("using defined custom config of Control-D resolver") + readBase64Config(resolverConfig.Ctrld.CustomConfig) + if err := v.Unmarshal(&cfg); err != nil { + log.Fatalf("failed to unmarshal config: %v", err) + } + } else { + cfg = ctrld.Config{} + cfg.Network = make(map[string]*ctrld.NetworkConfig) + cfg.Network["0"] = &ctrld.NetworkConfig{ + Name: "Network 0", + Cidrs: []string{"0.0.0.0/0"}, + } + cfg.Upstream = make(map[string]*ctrld.UpstreamConfig) + cfg.Upstream["0"] = &ctrld.UpstreamConfig{ + Endpoint: resolverConfig.DOH, + Type: ctrld.ResolverTypeDOH, + Timeout: 5000, + } + rules := make([]ctrld.Rule, 0, len(resolverConfig.Exclude)) + for _, domain := range resolverConfig.Exclude { + rules = append(rules, ctrld.Rule{domain: []string{}}) + } + cfg.Listener = make(map[string]*ctrld.ListenerConfig) + cfg.Listener["0"] = &ctrld.ListenerConfig{ + IP: "127.0.0.1", + Port: 53, + Policy: &ctrld.ListenerPolicyConfig{ + Name: "My Policy", + Rules: rules, + }, + } + processLogAndCacheFlags() } - processLogAndCacheFlags() if err := writeConfigFile(); err != nil { logger.Fatal().Err(err).Msg("failed to write config file") } else { diff --git a/internal/controld/config.go b/internal/controld/config.go index a77eecd..22c18b9 100644 --- a/internal/controld/config.go +++ b/internal/controld/config.go @@ -24,7 +24,10 @@ const ( // ResolverConfig represents Control D resolver data. type ResolverConfig struct { - DOH string `json:"doh"` + DOH string `json:"doh"` + Ctrld struct { + CustomConfig string `json:"custom_config"` + } `json:"ctrld"` Exclude []string `json:"exclude"` } From c7bad63869f6fc6247062f915426711f703a855a Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 26 Apr 2023 18:37:52 +0700 Subject: [PATCH 19/55] all: allow chosing random address and port for listener --- cmd/ctrld/dns_proxy.go | 76 +++++++++++++++++++++++++++++------------- config.go | 13 ++++++-- config_test.go | 9 ++++- docs/config.md | 8 ++--- 4 files changed, 74 insertions(+), 32 deletions(-) diff --git a/cmd/ctrld/dns_proxy.go b/cmd/ctrld/dns_proxy.go index a76f398..ec2ce30 100644 --- a/cmd/ctrld/dns_proxy.go +++ b/cmd/ctrld/dns_proxy.go @@ -9,6 +9,7 @@ import ( "runtime" "strconv" "strings" + "sync" "time" "github.com/miekg/dns" @@ -72,40 +73,37 @@ func (p *prog) serveDNS(listenerNum string) error { g, ctx := errgroup.WithContext(context.Background()) for _, proto := range []string{"udp", "tcp"} { proto := proto - // On Windows, there's no easy way for disabling/removing IPv6 DNS resolver, so we check whether we can - // listen on ::1, then spawn a listener for receiving DNS requests. if needLocalIPv6Listener() { g.Go(func() error { - s := &dns.Server{ - Addr: net.JoinHostPort("::1", strconv.Itoa(listenerConfig.Port)), - Net: proto, - Handler: handler, - } - go func() { - <-ctx.Done() - _ = s.Shutdown() - }() - if err := s.ListenAndServe(); err != nil { - mainLog.Error().Err(err).Msg("could not serving on ::1") + s, errCh := runDNSServer(net.JoinHostPort("::1", strconv.Itoa(listenerConfig.Port)), proto, handler) + defer s.Shutdown() + select { + case <-ctx.Done(): + case err := <-errCh: + // Local ipv6 listener should not terminate ctrld. + // It's a workaround for a quirk on Windows. + mainLog.Warn().Err(err).Msg("local ipv6 listener failed") } return nil }) } g.Go(func() error { - s := &dns.Server{ - Addr: dnsListenAddress(listenerConfig), - Net: proto, - Handler: handler, + s, errCh := runDNSServer(dnsListenAddress(listenerConfig), proto, handler) + defer s.Shutdown() + if listenerConfig.Port == 0 { + switch s.Net { + case "udp": + mainLog.Info().Msgf("Random port chosen for udp listener.%s: %s", listenerNum, s.PacketConn.LocalAddr()) + case "tcp": + mainLog.Info().Msgf("Random port chosen for tcp listener.%s: %s", listenerNum, s.Listener.Addr()) + } } - go func() { - <-ctx.Done() - _ = s.Shutdown() - }() - if err := s.ListenAndServe(); err != nil { - mainLog.Error().Err(err).Msgf("could not listen and serve on: %s", s.Addr) + select { + case <-ctx.Done(): + return nil + case err := <-errCh: return err } - return nil }) } return g.Wait() @@ -389,6 +387,8 @@ func ttlFromMsg(msg *dns.Msg) uint32 { } func needLocalIPv6Listener() bool { + // On Windows, there's no easy way for disabling/removing IPv6 DNS resolver, so we check whether we can + // listen on ::1, then spawn a listener for receiving DNS requests. return ctrldnet.SupportsIPv6ListenLocal() && runtime.GOOS == "windows" } @@ -412,3 +412,31 @@ func macFromMsg(msg *dns.Msg) string { } return "" } + +// runDNSServer starts a DNS server for given address and network, +// with the given handler. It ensures the server has started listening. +// Any error will be reported to the caller via returned channel. +// +// It's the caller responsibility to call Shutdown to close the server. +func runDNSServer(addr, network string, handler dns.Handler) (*dns.Server, <-chan error) { + s := &dns.Server{ + Addr: addr, + Net: network, + Handler: handler, + } + + waitLock := sync.Mutex{} + waitLock.Lock() + s.NotifyStartedFunc = waitLock.Unlock + + errCh := make(chan error) + go func() { + defer close(errCh) + if err := s.ListenAndServe(); err != nil { + mainLog.Error().Err(err).Msgf("could not listen and serve on: %s", s.Addr) + errCh <- err + } + }() + waitLock.Lock() + return s, errCh +} diff --git a/config.go b/config.go index 7e7bccb..9257351 100644 --- a/config.go +++ b/config.go @@ -133,8 +133,8 @@ type UpstreamConfig struct { // ListenerConfig specifies the networks configuration that ctrld will run on. type ListenerConfig struct { - IP string `mapstructure:"ip" toml:"ip,omitempty" validate:"ip"` - Port int `mapstructure:"port" toml:"port,omitempty" validate:"gt=0"` + IP string `mapstructure:"ip" toml:"ip,omitempty" validate:"iporempty"` + Port int `mapstructure:"port" toml:"port,omitempty" validate:"gte=0"` Restricted bool `mapstructure:"restricted" toml:"restricted,omitempty"` Policy *ListenerPolicyConfig `mapstructure:"policy" toml:"policy,omitempty"` } @@ -329,6 +329,7 @@ func (lc *ListenerConfig) Init() { // ValidateConfig validates the given config. func ValidateConfig(validate *validator.Validate, cfg *Config) error { _ = validate.RegisterValidation("dnsrcode", validateDnsRcode) + _ = validate.RegisterValidation("iporempty", validateIpOrEmpty) return validate.Struct(cfg) } @@ -336,6 +337,14 @@ func validateDnsRcode(fl validator.FieldLevel) bool { return dnsrcode.FromString(fl.Field().String()) != -1 } +func validateIpOrEmpty(fl validator.FieldLevel) bool { + val := fl.Field().String() + if val == "" { + return true + } + return net.ParseIP(val) != nil +} + func defaultPortFor(typ string) string { switch typ { case ResolverTypeDOH, ResolverTypeDOH3: diff --git a/config_test.go b/config_test.go index 90cd81a..ddbc97b 100644 --- a/config_test.go +++ b/config_test.go @@ -65,6 +65,7 @@ func TestConfigValidation(t *testing.T) { {"invalid Config", &ctrld.Config{}, true}, {"default Config", defaultConfig(t), false}, {"sample Config", testhelper.SampleConfig(t), false}, + {"empty listener IP", emptyListenerIP(t), false}, {"invalid cidr", invalidNetworkConfig(t), true}, {"invalid upstream type", invalidUpstreamType(t), true}, {"invalid upstream timeout", invalidUpstreamTimeout(t), true}, @@ -134,9 +135,15 @@ func invalidListenerIP(t *testing.T) *ctrld.Config { return cfg } +func emptyListenerIP(t *testing.T) *ctrld.Config { + cfg := defaultConfig(t) + cfg.Listener["0"].IP = "" + return cfg +} + func invalidListenerPort(t *testing.T) *ctrld.Config { cfg := defaultConfig(t) - cfg.Listener["0"].Port = 0 + cfg.Listener["0"].Port = -1 return cfg } diff --git a/docs/config.md b/docs/config.md index 4f12736..e8cec53 100644 --- a/docs/config.md +++ b/docs/config.md @@ -271,16 +271,14 @@ The `[listener]` section specifies the ip and port of the local DNS server. You ``` ### ip -IP address that serves the incoming requests. +IP address that serves the incoming requests. If `ip` is empty, ctrld will listen on all available addresses. -- Type: string -- Required: yes +- Type: ip address ### port -Port number that the listener will listen on for incoming requests. +Port number that the listener will listen on for incoming requests. If `port` is `0`, a random available port will be chosen. - Type: number -- Required: yes ### restricted If set to `true` makes the listener `REFUSE` DNS queries from all source IP addresses that are not explicitly defined in the policy using a `network`. From 68fe7e8406609e0ccc0077f9a9c30a5dc028b625 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 26 Apr 2023 19:44:44 +0700 Subject: [PATCH 20/55] cmd/ctrld: add "start --no-cd" flag to disable cd mode --- cmd/ctrld/cli.go | 18 ++++++++++++++++-- cmd/ctrld/main.go | 1 + 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 8dd5298..986623a 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -37,7 +37,13 @@ import ( "github.com/Control-D-Inc/ctrld/internal/router" ) -const selfCheckFQDN = "verify.controld.com" +const ( + selfCheckFQDN = "verify.controld.com" + cdModeConfigHeader = `# AUTO-GENERATED VIA CD FLAG - DO NOT MODIFY +# TO DISABLE AUTO-GENERATION RUN: ctrld service start --no-cd + +` +) var ( version = "dev" @@ -240,6 +246,7 @@ func initCLI() { runCmd.Flags().StringVarP(&logPath, "log", "", "", "Path to log file") runCmd.Flags().IntVarP(&cacheSize, "cache_size", "", 0, "Enable cache with size items") runCmd.Flags().StringVarP(&cdUID, "cd", "", "", "Control D resolver uid") + runCmd.Flags().BoolVarP(&noCD, "no-cd", "", false, `Disable cd mode, the same effect with --cd=""`) runCmd.Flags().StringVarP(&homedir, "homedir", "", "", "") _ = runCmd.Flags().MarkHidden("homedir") runCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`) @@ -292,6 +299,12 @@ func initCLI() { initLogging() cfg.Service.LogPath = logPath + if noCD { + cdUID = "" + if err := writeConfigFile(); err != nil { + log.Fatalf("failed to overwrite config file with --no-cd: %v", err) + } + } processCDFlags() // Explicitly passing config, so on system where home directory could not be obtained, @@ -349,6 +362,7 @@ func initCLI() { startCmd.Flags().StringVarP(&logPath, "log", "", "", "Path to log file") startCmd.Flags().IntVarP(&cacheSize, "cache_size", "", 0, "Enable cache with size items") startCmd.Flags().StringVarP(&cdUID, "cd", "", "", "Control D resolver uid") + startCmd.Flags().BoolVarP(&noCD, "no-cd", "", false, `Disable cd mode, the same effect with --cd=""`) startCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`) startCmd.Flags().BoolVarP(&setupRouter, "router", "", false, `setup for running on router platforms`) _ = startCmd.Flags().MarkHidden("router") @@ -565,7 +579,7 @@ func writeConfigFile() error { } defer f.Close() if cdUID != "" { - if _, err := f.WriteString("# AUTO-GENERATED VIA CD FLAG - DO NOT MODIFY\n\n"); err != nil { + if _, err := f.WriteString(cdModeConfigHeader); err != nil { return err } } diff --git a/cmd/ctrld/main.go b/cmd/ctrld/main.go index 79a5769..ce38a78 100644 --- a/cmd/ctrld/main.go +++ b/cmd/ctrld/main.go @@ -27,6 +27,7 @@ var ( cfg ctrld.Config verbose int cdUID string + noCD bool iface string ifaceStartStop string setupRouter bool From 2326160f2f05e5cc4d0e26bdd6a75a42c29f92c8 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 26 Apr 2023 21:32:08 +0700 Subject: [PATCH 21/55] Do not rely on unspecified assignment order of return statement See: https://github.com/golang/go/issues/58233 --- doh.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doh.go b/doh.go index e2e6586..931b6a1 100644 --- a/doh.go +++ b/doh.go @@ -85,7 +85,10 @@ func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro } answer := new(dns.Msg) - return answer, answer.Unpack(buf) + if err := answer.Unpack(buf); err != nil { + return nil, fmt.Errorf("answer.Unpack: %w", err) + } + return answer, nil } func addHeader(ctx context.Context, req *http.Request, sendClientInfo bool) { From 7bf231643bd8c98aa64869887b28d3fec825249e Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 27 Apr 2023 00:05:12 +0700 Subject: [PATCH 22/55] internal/router: normalize ip address from dnsmasq lease file dnsmasq may put an ip address with the interface index in lease file, causing bad data sent to the Control-D backend. --- internal/router/client_info.go | 12 +++++++++++- internal/router/client_info_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 internal/router/client_info_test.go diff --git a/internal/router/client_info.go b/internal/router/client_info.go index 05fafa2..4718a35 100644 --- a/internal/router/client_info.go +++ b/internal/router/client_info.go @@ -4,6 +4,7 @@ import ( "bytes" "log" "os" + "strings" "time" "github.com/fsnotify/fsnotify" @@ -80,9 +81,18 @@ func readClientInfoFile(name string) error { return lineread.File(name, func(line []byte) error { fields := bytes.Fields(line) mac := string(fields[1]) - ip := string(fields[2]) + ip := normalizeIP(string(fields[2])) hostname := string(fields[3]) r.mac.Store(mac, &ctrld.ClientInfo{Mac: mac, IP: ip, Hostname: hostname}) return nil }) } + +func normalizeIP(in string) string { + // dnsmasq may put ip with interface index in lease file, strip it here. + ip, _, found := strings.Cut(in, "%") + if found { + return ip + } + return in +} diff --git a/internal/router/client_info_test.go b/internal/router/client_info_test.go new file mode 100644 index 0000000..13b0648 --- /dev/null +++ b/internal/router/client_info_test.go @@ -0,0 +1,25 @@ +package router + +import "testing" + +func Test_normalizeIP(t *testing.T) { + tests := []struct { + name string + in string + want string + }{ + {"v4", "127.0.0.1", "127.0.0.1"}, + {"v4 with index", "127.0.0.1%lo", "127.0.0.1"}, + {"v6", "fe80::1", "fe80::1"}, + {"v6 with index", "fe80::1%22002", "fe80::1"}, + } + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + if got := normalizeIP(tc.in); got != tc.want { + t.Errorf("normalizeIP() = %v, want %v", got, tc.want) + } + }) + } +} From 411e23ecfecaaebdb02355fe5223f9f962726a78 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 27 Apr 2023 00:41:09 +0700 Subject: [PATCH 23/55] cmd/ctrld: fix missing content for default config When writing default config file, the content must be marshalled to the config object first before writing to disk. While at it, also use full path for default config file to make it clear to the user where the config is written. --- cmd/ctrld/cli.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 986623a..2d41994 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -608,10 +608,17 @@ func readConfigFile(writeDefaultConfig bool) bool { // If error is viper.ConfigFileNotFoundError, write default config. if _, ok := err.(viper.ConfigFileNotFoundError); ok { + if err := v.Unmarshal(&cfg); err != nil { + log.Fatalf("failed to unmarshal default config: %v", err) + } if err := writeConfigFile(); err != nil { log.Fatalf("failed to write default config file: %v", err) } else { - log.Println("writing default config file to: " + defaultConfigFile) + fp, err := filepath.Abs(defaultConfigFile) + if err != nil { + log.Fatalf("failed to get default config file path: %v", err) + } + log.Println("writing default config file to: " + fp) } defaultConfigWritten = true return false From 5528ac8bf1a003028d7cd2c5fb79a84823757e45 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 27 Apr 2023 00:55:37 +0700 Subject: [PATCH 24/55] internal/router: log invalid ip address entry --- internal/router/client_info.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/router/client_info.go b/internal/router/client_info.go index 4718a35..dff4ede 100644 --- a/internal/router/client_info.go +++ b/internal/router/client_info.go @@ -3,6 +3,7 @@ package router import ( "bytes" "log" + "net" "os" "strings" "time" @@ -82,6 +83,10 @@ func readClientInfoFile(name string) error { fields := bytes.Fields(line) mac := string(fields[1]) ip := normalizeIP(string(fields[2])) + if net.ParseIP(ip) == nil { + log.Printf("invalid ip address entry: %q", ip) + ip = "" + } hostname := string(fields[3]) r.mac.Store(mac, &ctrld.ClientInfo{Mac: mac, IP: ip, Hostname: hostname}) return nil From 31239684c7f1c5fd444000cd0262adfecf62fb04 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 27 Apr 2023 20:09:44 +0700 Subject: [PATCH 25/55] Revert "cmd/ctrld: add "start --no-cd" flag to disable cd mode" This reverts commit 00fe7f59d13774f2ea6c325bdbb8165be58a1edd. The purpose is disable cd mode for already installed service, which is a hard problem than we thought. So leave it out of v1.2 cycle. --- cmd/ctrld/cli.go | 18 ++---------------- cmd/ctrld/main.go | 1 - 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 2d41994..cad5b3d 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -37,13 +37,7 @@ import ( "github.com/Control-D-Inc/ctrld/internal/router" ) -const ( - selfCheckFQDN = "verify.controld.com" - cdModeConfigHeader = `# AUTO-GENERATED VIA CD FLAG - DO NOT MODIFY -# TO DISABLE AUTO-GENERATION RUN: ctrld service start --no-cd - -` -) +const selfCheckFQDN = "verify.controld.com" var ( version = "dev" @@ -246,7 +240,6 @@ func initCLI() { runCmd.Flags().StringVarP(&logPath, "log", "", "", "Path to log file") runCmd.Flags().IntVarP(&cacheSize, "cache_size", "", 0, "Enable cache with size items") runCmd.Flags().StringVarP(&cdUID, "cd", "", "", "Control D resolver uid") - runCmd.Flags().BoolVarP(&noCD, "no-cd", "", false, `Disable cd mode, the same effect with --cd=""`) runCmd.Flags().StringVarP(&homedir, "homedir", "", "", "") _ = runCmd.Flags().MarkHidden("homedir") runCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`) @@ -299,12 +292,6 @@ func initCLI() { initLogging() cfg.Service.LogPath = logPath - if noCD { - cdUID = "" - if err := writeConfigFile(); err != nil { - log.Fatalf("failed to overwrite config file with --no-cd: %v", err) - } - } processCDFlags() // Explicitly passing config, so on system where home directory could not be obtained, @@ -362,7 +349,6 @@ func initCLI() { startCmd.Flags().StringVarP(&logPath, "log", "", "", "Path to log file") startCmd.Flags().IntVarP(&cacheSize, "cache_size", "", 0, "Enable cache with size items") startCmd.Flags().StringVarP(&cdUID, "cd", "", "", "Control D resolver uid") - startCmd.Flags().BoolVarP(&noCD, "no-cd", "", false, `Disable cd mode, the same effect with --cd=""`) startCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`) startCmd.Flags().BoolVarP(&setupRouter, "router", "", false, `setup for running on router platforms`) _ = startCmd.Flags().MarkHidden("router") @@ -579,7 +565,7 @@ func writeConfigFile() error { } defer f.Close() if cdUID != "" { - if _, err := f.WriteString(cdModeConfigHeader); err != nil { + if _, err := f.WriteString("# AUTO-GENERATED VIA CD FLAG - DO NOT MODIFY\n\n"); err != nil { return err } } diff --git a/cmd/ctrld/main.go b/cmd/ctrld/main.go index ce38a78..79a5769 100644 --- a/cmd/ctrld/main.go +++ b/cmd/ctrld/main.go @@ -27,7 +27,6 @@ var ( cfg ctrld.Config verbose int cdUID string - noCD bool iface string ifaceStartStop string setupRouter bool From 43fecdf60f94b0cc32a2295f4694ac1d194b39af Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 27 Apr 2023 20:43:01 +0700 Subject: [PATCH 26/55] all: log when client info included in the request --- cmd/ctrld/dns_proxy.go | 1 + doh.go | 1 + 2 files changed, 2 insertions(+) diff --git a/cmd/ctrld/dns_proxy.go b/cmd/ctrld/dns_proxy.go index ec2ce30..4a1685d 100644 --- a/cmd/ctrld/dns_proxy.go +++ b/cmd/ctrld/dns_proxy.go @@ -237,6 +237,7 @@ func (p *prog) proxy(ctx context.Context, upstreams []string, failoverRcodes []i if upstreamConfig.UpstreamSendClientInfo() { ci := router.GetClientInfoByMac(macFromMsg(msg)) if ci != nil { + ctrld.Log(ctx, mainLog.Debug(), "including client info with the request") ctx = context.WithValue(ctx, ctrld.ClientInfoCtxKey{}, ci) } } diff --git a/doh.go b/doh.go index 931b6a1..242f759 100644 --- a/doh.go +++ b/doh.go @@ -107,4 +107,5 @@ func addHeader(ctx context.Context, req *http.Request, sendClientInfo bool) { } } } + Log(ctx, ProxyLog.Debug().Interface("header", req.Header), "sending request header") } From a7ea20b117a1ffc3187bfad651e77d3833b62bd0 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 28 Apr 2023 01:23:06 +0700 Subject: [PATCH 27/55] cmd/ctrld: ensure runDNSServer returns when error happens --- cmd/ctrld/dns_proxy.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/ctrld/dns_proxy.go b/cmd/ctrld/dns_proxy.go index 4a1685d..845c2e2 100644 --- a/cmd/ctrld/dns_proxy.go +++ b/cmd/ctrld/dns_proxy.go @@ -434,6 +434,7 @@ func runDNSServer(addr, network string, handler dns.Handler) (*dns.Server, <-cha go func() { defer close(errCh) if err := s.ListenAndServe(); err != nil { + waitLock.Unlock() mainLog.Error().Err(err).Msgf("could not listen and serve on: %s", s.Addr) errCh <- err } From 07689954bf5c77632a88c4639a41df92ab2a4854 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 28 Apr 2023 09:40:47 +0700 Subject: [PATCH 28/55] cmd/ctrld: change default log level to warn --- cmd/ctrld/main.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd/ctrld/main.go b/cmd/ctrld/main.go index 79a5769..793ca95 100644 --- a/cmd/ctrld/main.go +++ b/cmd/ctrld/main.go @@ -86,9 +86,12 @@ func initLogging() { // TODO: find a better way. ctrld.ProxyLog = mainLog - zerolog.SetGlobalLevel(zerolog.InfoLevel) + zerolog.SetGlobalLevel(zerolog.WarnLevel) logLevel := cfg.Service.LogLevel - if verbose > 1 { + switch { + case verbose == 1: + logLevel = "info" + case verbose > 1: logLevel = "debug" } if logLevel == "" { From 02fa7fbe2e8e7aea3717adadc62303caf7a2662d Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Sat, 29 Apr 2023 00:16:14 +0700 Subject: [PATCH 29/55] Workaround issue with weird DNS server when bootstraping We see in practice on fresh new VM test, there's a DNS server that return the answer with record not for the query domain. To workaround this, filter out the answers not for the query domain. --- resolver.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/resolver.go b/resolver.go index 084281c..f2a8424 100644 --- a/resolver.go +++ b/resolver.go @@ -140,11 +140,18 @@ func lookupIP(domain string, timeout int, withBootstrapDNS bool) (ips []string) if timeout > 0 && timeout < timeoutMs { timeoutMs = timeoutMs } + questionDomain := dns.Fqdn(domain) ipFromRecord := func(record dns.RR) string { switch ar := record.(type) { case *dns.A: + if ar.Hdr.Name != questionDomain { + return "" + } return ar.A.String() case *dns.AAAA: + if ar.Hdr.Name != questionDomain { + return "" + } return ar.AAAA.String() } return "" @@ -154,7 +161,7 @@ func lookupIP(domain string, timeout int, withBootstrapDNS bool) (ips []string) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutMs)*time.Millisecond) defer cancel() m := new(dns.Msg) - m.SetQuestion(domain+".", dnsType) + m.SetQuestion(questionDomain, dnsType) m.RecursionDesired = true r, err := resolver.Resolve(ctx, m) From d57c1d6d44c698cfdaab7be68076cc417d9eae50 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Sat, 29 Apr 2023 03:19:56 +0700 Subject: [PATCH 30/55] Workaround for DOH broken transport when network changes When network changes, for example: connect/disconnect VPN, the old connection will become broken, but still can be re-used for new requests. That would cause un-necessary delay for ctrld clients: - Time 0 - do request with broken transport, 5s timeout. - Time 0.5 - network stack become usable. - Time 5 - timeout reached. - Time 5.1 - do request with new transport -> success. Instead, we can do two requests in parallel, with the failover one using a fresh new transport. So if the main one is broken, we still can get the result from the failover one. --- doh.go | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 94 insertions(+), 7 deletions(-) diff --git a/doh.go b/doh.go index 242f759..367e419 100644 --- a/doh.go +++ b/doh.go @@ -8,6 +8,7 @@ import ( "io" "net/http" "net/url" + "runtime" "github.com/miekg/dns" ) @@ -27,6 +28,7 @@ func newDohResolver(uc *UpstreamConfig) *dohResolver { transport: uc.transport, http3RoundTripper: uc.http3RoundTripper, sendClientInfo: uc.UpstreamSendClientInfo(), + uc: uc, } return r } @@ -37,6 +39,7 @@ type dohResolver struct { transport *http.Transport http3RoundTripper http.RoundTripper sendClientInfo bool + uc *UpstreamConfig } func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) { @@ -57,14 +60,12 @@ func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro } addHeader(ctx, req, r.sendClientInfo) - c := http.Client{Transport: r.transport} - if r.isDoH3 { - if r.http3RoundTripper == nil { - return nil, errors.New("DoH3 is not supported") - } - c.Transport = r.http3RoundTripper + var resp *http.Response + if runtime.GOOS == "linux" { + resp, err = r.doRequestWithFailover(req) + } else { + resp, err = r.doRequest(req) } - resp, err := c.Do(req) if err != nil { if r.isDoH3 { if closer, ok := r.http3RoundTripper.(io.Closer); ok { @@ -91,6 +92,92 @@ func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro return answer, nil } +func (r *dohResolver) doRequest(req *http.Request) (*http.Response, error) { + c := http.Client{Transport: r.transport} + if r.isDoH3 { + if r.http3RoundTripper == nil { + return nil, errors.New("DoH3 is not supported") + } + c.Transport = r.http3RoundTripper + } + return c.Do(req) +} + +func (r *dohResolver) doRequestWithFailover(req *http.Request) (*http.Response, error) { + // To deal with network changes, for example, connect/disconnect to VPN, + // We use two clients: + // + // - mainClient: use the current transport. + // - failoverClient: use a clone of the current transport. + // + // Two clients will perform the requests concurrently, but with mainClient + // started first. So in normal condition, mainClient is likely to return first, + // and we will use its result. In case of mainClient failed, we trigger the + // re-bootstrapping process, and use the result from failover client. + mainClient := http.Client{Transport: r.transport} + failoverClient := http.Client{} + if r.isDoH3 { + if r.http3RoundTripper == nil { + return nil, errors.New("DoH3 is not supported") + } + // TODO: figure out how to clone DOH3 round tripper? + mainClient.Transport = r.http3RoundTripper + failoverClient.Transport = r.http3RoundTripper + } else { + failoverClient.Transport = r.transport.Clone() + } + + done := make(chan struct{}) + defer close(done) + + type result struct { + resp *http.Response + err error + } + + respCh := make(chan result) + doRequest := func(client *http.Client) { + resp, err := client.Do(req) + select { + case respCh <- result{resp: resp, err: err}: + case <-done: + if client == &mainClient && err != nil { + r.uc.ReBootstrap() + } + if resp != nil { + defer resp.Body.Close() + _, _ = io.Copy(io.Discard, resp.Body) + } + } + } + + mainClientStarted := make(chan struct{}) + go func() { + // Notify failoverClient that mainClient started. + close(mainClientStarted) + doRequest(&mainClient) + }() + go func() { + // Wait mainClient started first. + <-mainClientStarted + doRequest(&failoverClient) + }() + + var ( + resp *http.Response + err error + ) + for range []*http.Client{&mainClient, &failoverClient} { + res := <-respCh + if res.err == nil { + resp = res.resp + break + } + err = res.err + } + return resp, err +} + func addHeader(ctx context.Context, req *http.Request, sendClientInfo bool) { req.Header.Set("Content-Type", headerApplicationDNS) req.Header.Set("Accept", headerApplicationDNS) From 56d8dc865f37f6d22feae21bcf5296b68d3c9a9d Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Sat, 29 Apr 2023 13:15:10 +0700 Subject: [PATCH 31/55] Use different failover mechanism on Linux Instead of always doubling the request, first we wrap the request with a failover timeout, 500ms, which is an average time for a normal request. If this request failed, trigger re-bootstrapping and retry the request. --- doh.go | 82 ++++++++++++---------------------------------------------- 1 file changed, 16 insertions(+), 66 deletions(-) diff --git a/doh.go b/doh.go index 367e419..76f81ea 100644 --- a/doh.go +++ b/doh.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" "runtime" + "time" "github.com/miekg/dns" ) @@ -103,79 +104,28 @@ func (r *dohResolver) doRequest(req *http.Request) (*http.Response, error) { return c.Do(req) } +const failoverTimeout = 500 * time.Millisecond + +// doRequestWithFailover is like doRequest, but wrap the request with initial timeout. +// If the first request failed, it's likely that the transport was broken, then trigger +// re-bootstrapping and retry the request. func (r *dohResolver) doRequestWithFailover(req *http.Request) (*http.Response, error) { - // To deal with network changes, for example, connect/disconnect to VPN, - // We use two clients: - // - // - mainClient: use the current transport. - // - failoverClient: use a clone of the current transport. - // - // Two clients will perform the requests concurrently, but with mainClient - // started first. So in normal condition, mainClient is likely to return first, - // and we will use its result. In case of mainClient failed, we trigger the - // re-bootstrapping process, and use the result from failover client. - mainClient := http.Client{Transport: r.transport} - failoverClient := http.Client{} + c := http.Client{Transport: r.transport} if r.isDoH3 { if r.http3RoundTripper == nil { return nil, errors.New("DoH3 is not supported") } - // TODO: figure out how to clone DOH3 round tripper? - mainClient.Transport = r.http3RoundTripper - failoverClient.Transport = r.http3RoundTripper - } else { - failoverClient.Transport = r.transport.Clone() + c.Transport = r.http3RoundTripper } - - done := make(chan struct{}) - defer close(done) - - type result struct { - resp *http.Response - err error + ctx, cancel := context.WithTimeout(context.Background(), failoverTimeout) + defer cancel() + resp, err := c.Do(req.WithContext(ctx)) + if err == nil { + return resp, err } - - respCh := make(chan result) - doRequest := func(client *http.Client) { - resp, err := client.Do(req) - select { - case respCh <- result{resp: resp, err: err}: - case <-done: - if client == &mainClient && err != nil { - r.uc.ReBootstrap() - } - if resp != nil { - defer resp.Body.Close() - _, _ = io.Copy(io.Discard, resp.Body) - } - } - } - - mainClientStarted := make(chan struct{}) - go func() { - // Notify failoverClient that mainClient started. - close(mainClientStarted) - doRequest(&mainClient) - }() - go func() { - // Wait mainClient started first. - <-mainClientStarted - doRequest(&failoverClient) - }() - - var ( - resp *http.Response - err error - ) - for range []*http.Client{&mainClient, &failoverClient} { - res := <-respCh - if res.err == nil { - resp = res.resp - break - } - err = res.err - } - return resp, err + r.uc.ReBootstrap() + c.Transport = r.uc.transport + return c.Do(req) } func addHeader(ctx context.Context, req *http.Request, sendClientInfo bool) { From 5cad0d6be1549ee92076d0ec35ed12d8d927937b Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Sat, 29 Apr 2023 13:56:18 +0700 Subject: [PATCH 32/55] all: watch link state on Linux using netlink So we can detect changed to link and trigger re-bootstrap. --- cmd/ctrld/netlink_linux.go | 27 +++++++++++++++++++ cmd/ctrld/netlink_others.go | 5 ++++ cmd/ctrld/prog.go | 2 ++ config.go | 4 +++ config_quic.go | 2 ++ doh.go | 54 ++++++++----------------------------- go.mod | 2 ++ go.sum | 7 +++++ 8 files changed, 60 insertions(+), 43 deletions(-) create mode 100644 cmd/ctrld/netlink_linux.go create mode 100644 cmd/ctrld/netlink_others.go diff --git a/cmd/ctrld/netlink_linux.go b/cmd/ctrld/netlink_linux.go new file mode 100644 index 0000000..86eb45b --- /dev/null +++ b/cmd/ctrld/netlink_linux.go @@ -0,0 +1,27 @@ +package main + +import ( + "github.com/vishvananda/netlink" + "golang.org/x/sys/unix" +) + +func (p *prog) watchLinkState() { + ch := make(chan netlink.LinkUpdate) + done := make(chan struct{}) + defer close(done) + if err := netlink.LinkSubscribe(ch, done); err != nil { + mainLog.Warn().Err(err).Msg("could not subscribe link") + return + } + for lu := range ch { + if lu.Change == 0xFFFFFFFF { + continue + } + if lu.Change&unix.IFF_UP != 0 { + mainLog.Debug().Msgf("link state changed, re-bootstrapping") + for _, uc := range p.cfg.Upstream { + uc.ReBootstrap() + } + } + } +} diff --git a/cmd/ctrld/netlink_others.go b/cmd/ctrld/netlink_others.go new file mode 100644 index 0000000..d069661 --- /dev/null +++ b/cmd/ctrld/netlink_others.go @@ -0,0 +1,5 @@ +//go:build !linux + +package main + +func (p *prog) watchLinkState() {} diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index 9aba028..a170b36 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -82,6 +82,8 @@ func (p *prog) run() { uc.SetupTransport() } + go p.watchLinkState() + for listenerNum := range p.cfg.Listener { p.cfg.Listener[listenerNum].Init() go func(listenerNum string) { diff --git a/config.go b/config.go index 9257351..4eb4e56 100644 --- a/config.go +++ b/config.go @@ -9,6 +9,7 @@ import ( "net/url" "os" "strings" + "sync" "sync/atomic" "time" @@ -127,6 +128,7 @@ type UpstreamConfig struct { u *url.URL `mapstructure:"-" toml:"-"` g singleflight.Group + mu sync.Mutex bootstrapIPs []string nextBootstrapIP atomic.Uint32 } @@ -268,6 +270,8 @@ func (uc *UpstreamConfig) setupDOHTransport() { } func (uc *UpstreamConfig) setupDOHTransportWithoutPingUpstream() { + uc.mu.Lock() + defer uc.mu.Unlock() uc.transport = http.DefaultTransport.(*http.Transport).Clone() uc.transport.IdleConnTimeout = 5 * time.Second uc.transport.TLSClientConfig = &tls.Config{RootCAs: uc.certPool} diff --git a/config_quic.go b/config_quic.go index 4b9f4c9..9c6d668 100644 --- a/config_quic.go +++ b/config_quic.go @@ -19,6 +19,8 @@ func (uc *UpstreamConfig) setupDOH3Transport() { } func (uc *UpstreamConfig) setupDOH3TransportWithoutPingUpstream() { + uc.mu.Lock() + defer uc.mu.Unlock() rt := &http3.RoundTripper{} rt.TLSClientConfig = &tls.Config{RootCAs: uc.certPool} rt.Dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { diff --git a/doh.go b/doh.go index 76f81ea..000cc49 100644 --- a/doh.go +++ b/doh.go @@ -8,8 +8,6 @@ import ( "io" "net/http" "net/url" - "runtime" - "time" "github.com/miekg/dns" ) @@ -23,10 +21,13 @@ const ( ) func newDohResolver(uc *UpstreamConfig) *dohResolver { + uc.mu.Lock() + transport := uc.transport + uc.mu.Unlock() r := &dohResolver{ endpoint: uc.u, isDoH3: uc.Type == ResolverTypeDOH3, - transport: uc.transport, + transport: transport, http3RoundTripper: uc.http3RoundTripper, sendClientInfo: uc.UpstreamSendClientInfo(), uc: uc, @@ -61,12 +62,14 @@ func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro } addHeader(ctx, req, r.sendClientInfo) - var resp *http.Response - if runtime.GOOS == "linux" { - resp, err = r.doRequestWithFailover(req) - } else { - resp, err = r.doRequest(req) + c := http.Client{Transport: r.transport} + if r.isDoH3 { + if r.http3RoundTripper == nil { + return nil, errors.New("DoH3 is not supported") + } + c.Transport = r.http3RoundTripper } + resp, err := c.Do(req) if err != nil { if r.isDoH3 { if closer, ok := r.http3RoundTripper.(io.Closer); ok { @@ -93,41 +96,6 @@ func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro return answer, nil } -func (r *dohResolver) doRequest(req *http.Request) (*http.Response, error) { - c := http.Client{Transport: r.transport} - if r.isDoH3 { - if r.http3RoundTripper == nil { - return nil, errors.New("DoH3 is not supported") - } - c.Transport = r.http3RoundTripper - } - return c.Do(req) -} - -const failoverTimeout = 500 * time.Millisecond - -// doRequestWithFailover is like doRequest, but wrap the request with initial timeout. -// If the first request failed, it's likely that the transport was broken, then trigger -// re-bootstrapping and retry the request. -func (r *dohResolver) doRequestWithFailover(req *http.Request) (*http.Response, error) { - c := http.Client{Transport: r.transport} - if r.isDoH3 { - if r.http3RoundTripper == nil { - return nil, errors.New("DoH3 is not supported") - } - c.Transport = r.http3RoundTripper - } - ctx, cancel := context.WithTimeout(context.Background(), failoverTimeout) - defer cancel() - resp, err := c.Do(req.WithContext(ctx)) - if err == nil { - return resp, err - } - r.uc.ReBootstrap() - c.Transport = r.uc.transport - return c.Do(req) -} - func addHeader(ctx context.Context, req *http.Request, sendClientInfo bool) { req.Header.Set("Content-Type", headerApplicationDNS) req.Header.Set("Accept", headerApplicationDNS) diff --git a/go.mod b/go.mod index 1624260..1d1788c 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/spf13/cobra v1.4.0 github.com/spf13/viper v1.14.0 github.com/stretchr/testify v1.8.1 + github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 golang.org/x/net v0.7.0 golang.org/x/sync v0.1.0 golang.org/x/sys v0.5.0 @@ -65,6 +66,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect + github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect go4.org/mem v0.0.0-20210711025021-927187094b94 // indirect golang.org/x/crypto v0.6.0 // indirect golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect diff --git a/go.sum b/go.sum index c3cb4be..6aedff1 100644 --- a/go.sum +++ b/go.sum @@ -282,6 +282,11 @@ github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/u-root/uio v0.0.0-20221213070652-c3537552635f h1:dpx1PHxYqAnXzbryJrWP1NQLzEjwcVgFLhkknuFQ7ww= github.com/u-root/uio v0.0.0-20221213070652-c3537552635f/go.mod h1:IogEAUBXDEwX7oR/BMmCctShYs80ql4hF0ySdzGxf7E= +github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 h1:8mhqcHPqTMhSPoslhGYihEgSfc77+7La1P6kiB6+9So= +github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= +github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -429,6 +434,7 @@ golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -437,6 +443,7 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From b267572b3885ee7b763dfb28b1f1d86c15ced266 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 28 Apr 2023 01:12:59 +0700 Subject: [PATCH 33/55] all: implement split upstreams This commit introduces split upstreams feature, allowing to configure what ip stack that ctrld will use to connect to upstream. --- cmd/ctrld/dns_proxy.go | 3 +- config.go | 206 ++++++++++++++++++++++++++++++++-------- config_internal_test.go | 9 ++ config_quic.go | 56 +++++++++-- config_quic_free.go | 5 +- docs/config.md | 20 +++- doh.go | 16 ++-- doq.go | 14 ++- dot.go | 10 +- resolver.go | 15 ++- 10 files changed, 286 insertions(+), 68 deletions(-) diff --git a/cmd/ctrld/dns_proxy.go b/cmd/ctrld/dns_proxy.go index 845c2e2..f473634 100644 --- a/cmd/ctrld/dns_proxy.go +++ b/cmd/ctrld/dns_proxy.go @@ -242,7 +242,8 @@ func (p *prog) proxy(ctx context.Context, upstreams []string, failoverRcodes []i } } answer, err := resolve1(n, upstreamConfig, msg) - if err != nil { + // Only do re-bootstrapping if bootstrap ip is not explicitly set by user. + if err != nil && upstreamConfig.BootstrapIP == "" { ctrld.Log(ctx, mainLog.Debug().Err(err), "could not resolve query on first attempt, retrying...") // If any error occurred, re-bootstrap transport/ip, retry the request. upstreamConfig.ReBootstrap() diff --git a/config.go b/config.go index 4eb4e56..f3e28fc 100644 --- a/config.go +++ b/config.go @@ -4,13 +4,13 @@ import ( "context" "crypto/tls" "crypto/x509" + "math/rand" "net" "net/http" "net/url" "os" "strings" "sync" - "sync/atomic" "time" "github.com/go-playground/validator/v10" @@ -22,6 +22,15 @@ import ( ctrldnet "github.com/Control-D-Inc/ctrld/internal/net" ) +const ( + IpStackBoth = "both" + IpStackV4 = "v4" + IpStackV6 = "v6" + IpStackSplit = "split" +) + +var controldParentDomains = []string{"controld.com", "controld.net", "controld.dev"} + // SetConfigName set the config name that ctrld will look for. // DEPRECATED: use SetConfigNameWithPath instead. func SetConfigName(v *viper.Viper, name string) { @@ -118,19 +127,25 @@ type UpstreamConfig struct { Endpoint string `mapstructure:"endpoint" toml:"endpoint,omitempty" validate:"required_unless=Type os"` BootstrapIP string `mapstructure:"bootstrap_ip" toml:"bootstrap_ip,omitempty"` Domain string `mapstructure:"-" toml:"-"` + IPStack string `mapstructure:"ip_stack" toml:"ip_stack,omitempty" validate:"ipstack"` Timeout int `mapstructure:"timeout" toml:"timeout,omitempty" validate:"gte=0"` // The caller should not access this field directly. // Use UpstreamSendClientInfo instead. - SendClientInfo *bool `mapstructure:"send_client_info" toml:"send_client_info,omitempty"` - transport *http.Transport `mapstructure:"-" toml:"-"` - http3RoundTripper http.RoundTripper `mapstructure:"-" toml:"-"` - certPool *x509.CertPool `mapstructure:"-" toml:"-"` - u *url.URL `mapstructure:"-" toml:"-"` + SendClientInfo *bool `mapstructure:"send_client_info" toml:"send_client_info,omitempty"` - g singleflight.Group - mu sync.Mutex - bootstrapIPs []string - nextBootstrapIP atomic.Uint32 + g singleflight.Group + mu sync.Mutex + bootstrapIPs []string + bootstrapIPs4 []string + bootstrapIPs6 []string + transport *http.Transport + transport4 *http.Transport + transport6 *http.Transport + http3RoundTripper http.RoundTripper + http3RoundTripper4 http.RoundTripper + http3RoundTripper6 http.RoundTripper + certPool *x509.CertPool + u *url.URL } // ListenerConfig specifies the networks configuration that ctrld will run on. @@ -164,18 +179,23 @@ func (uc *UpstreamConfig) Init() { uc.u = u } } - if uc.Domain != "" { - return + if uc.Domain == "" { + if !strings.Contains(uc.Endpoint, ":") { + uc.Domain = uc.Endpoint + uc.Endpoint = net.JoinHostPort(uc.Endpoint, defaultPortFor(uc.Type)) + } + host, _, _ := net.SplitHostPort(uc.Endpoint) + uc.Domain = host + if net.ParseIP(uc.Domain) != nil { + uc.BootstrapIP = uc.Domain + } } - - if !strings.Contains(uc.Endpoint, ":") { - uc.Domain = uc.Endpoint - uc.Endpoint = net.JoinHostPort(uc.Endpoint, defaultPortFor(uc.Type)) - } - host, _, _ := net.SplitHostPort(uc.Endpoint) - uc.Domain = host - if net.ParseIP(uc.Domain) != nil { - uc.BootstrapIP = uc.Domain + if uc.IPStack == "" { + if uc.isControlD() { + uc.IPStack = IpStackSplit + } else { + uc.IPStack = IpStackBoth + } } } @@ -195,13 +215,8 @@ func (uc *UpstreamConfig) UpstreamSendClientInfo() bool { } switch uc.Type { case ResolverTypeDOH, ResolverTypeDOH3: - if u, err := url.Parse(uc.Endpoint); err == nil { - domain := u.Hostname() - for _, parent := range []string{"controld.com", "controld.net"} { - if dns.IsSubDomain(parent, domain) { - return true - } - } + if uc.isControlD() { + return true } } return false @@ -226,6 +241,13 @@ func (uc *UpstreamConfig) SetupBootstrapIP() { // The first usable IP will be used as bootstrap IP of the upstream. func (uc *UpstreamConfig) setupBootstrapIP(withBootstrapDNS bool) { uc.bootstrapIPs = lookupIP(uc.Domain, uc.Timeout, withBootstrapDNS) + for _, ip := range uc.bootstrapIPs { + if ctrldnet.IsIPv6(ip) { + uc.bootstrapIPs6 = append(uc.bootstrapIPs6, ip) + } else { + uc.bootstrapIPs4 = append(uc.bootstrapIPs4, ip) + } + } ProxyLog.Debug().Msgf("Bootstrap IPs: %v", uc.bootstrapIPs) } @@ -238,7 +260,6 @@ func (uc *UpstreamConfig) ReBootstrap() { } _, _, _ = uc.g.Do("ReBootstrap", func() (any, error) { ProxyLog.Debug().Msg("re-bootstrapping upstream ip") - uc.BootstrapIP = "" uc.setupTransportWithoutPingUpstream() return true, nil }) @@ -269,19 +290,17 @@ func (uc *UpstreamConfig) setupDOHTransport() { uc.pingUpstream() } -func (uc *UpstreamConfig) setupDOHTransportWithoutPingUpstream() { - uc.mu.Lock() - defer uc.mu.Unlock() - uc.transport = http.DefaultTransport.(*http.Transport).Clone() - uc.transport.IdleConnTimeout = 5 * time.Second - uc.transport.TLSClientConfig = &tls.Config{RootCAs: uc.certPool} +func (uc *UpstreamConfig) newDOHTransport(addrs []string) *http.Transport { + transport := http.DefaultTransport.(*http.Transport).Clone() + transport.IdleConnTimeout = 5 * time.Second + transport.TLSClientConfig = &tls.Config{RootCAs: uc.certPool} dialerTimeoutMs := 2000 if uc.Timeout > 0 && uc.Timeout < dialerTimeoutMs { dialerTimeoutMs = uc.Timeout } dialerTimeout := time.Duration(dialerTimeoutMs) * time.Millisecond - uc.transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { _, port, _ := net.SplitHostPort(addr) if uc.BootstrapIP != "" { dialer := net.Dialer{Timeout: dialerTimeout, KeepAlive: dialerTimeout} @@ -292,17 +311,42 @@ func (uc *UpstreamConfig) setupDOHTransportWithoutPingUpstream() { pd := &ctrldnet.ParallelDialer{} pd.Timeout = dialerTimeout pd.KeepAlive = dialerTimeout - addrs := make([]string, len(uc.bootstrapIPs)) - for i := range uc.bootstrapIPs { - addrs[i] = net.JoinHostPort(uc.bootstrapIPs[i], port) + dialAddrs := make([]string, len(addrs)) + for i := range addrs { + dialAddrs[i] = net.JoinHostPort(addrs[i], port) } - conn, err := pd.DialContext(ctx, network, addrs) + conn, err := pd.DialContext(ctx, network, dialAddrs) if err != nil { return nil, err } Log(ctx, ProxyLog.Debug(), "sending doh request to: %s", conn.RemoteAddr()) return conn, nil } + return transport +} + +func (uc *UpstreamConfig) setupDOHTransportWithoutPingUpstream() { + uc.mu.Lock() + defer uc.mu.Unlock() + switch uc.IPStack { + case IpStackBoth, "": + uc.transport = uc.newDOHTransport(uc.bootstrapIPs) + case IpStackV4: + uc.transport = uc.newDOHTransport(uc.bootstrapIPs4) + case IpStackV6: + uc.transport = uc.newDOHTransport(uc.bootstrapIPs6) + case IpStackSplit: + uc.transport4 = uc.newDOHTransport(uc.bootstrapIPs4) + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + if ctrldnet.IPv6Available(ctx) { + uc.transport6 = uc.newDOHTransport(uc.bootstrapIPs6) + } else { + uc.transport6 = uc.transport4 + } + + uc.transport = uc.newDOHTransport(uc.bootstrapIPs) + } } func (uc *UpstreamConfig) pingUpstream() { @@ -320,6 +364,74 @@ func (uc *UpstreamConfig) pingUpstream() { _, _ = dnsResolver.Resolve(ctx, msg) } +func (uc *UpstreamConfig) isControlD() bool { + domain := uc.Domain + if domain == "" { + if u, err := url.Parse(uc.Endpoint); err == nil { + domain = u.Hostname() + } + } + for _, parent := range controldParentDomains { + if dns.IsSubDomain(parent, domain) { + return true + } + } + return false +} + +func (uc *UpstreamConfig) dohTransport(dnsType uint16) http.RoundTripper { + switch uc.IPStack { + case IpStackBoth, IpStackV4, IpStackV6: + return uc.transport + case IpStackSplit: + switch dnsType { + case dns.TypeA: + return uc.transport4 + default: + return uc.transport6 + } + } + return uc.transport +} + +func (uc *UpstreamConfig) bootstrapIPForDNSType(dnsType uint16) string { + switch uc.IPStack { + case IpStackBoth: + return pick(uc.bootstrapIPs) + case IpStackV4: + return pick(uc.bootstrapIPs4) + case IpStackV6: + return pick(uc.bootstrapIPs6) + case IpStackSplit: + switch dnsType { + case dns.TypeA: + return pick(uc.bootstrapIPs4) + default: + return pick(uc.bootstrapIPs6) + } + } + return pick(uc.bootstrapIPs) +} + +func (uc *UpstreamConfig) netForDNSType(dnsType uint16) (string, string) { + switch uc.IPStack { + case IpStackBoth: + return "tcp-tls", "udp" + case IpStackV4: + return "tcp4-tls", "udp4" + case IpStackV6: + return "tcp6-tls", "udp6" + case IpStackSplit: + switch dnsType { + case dns.TypeA: + return "tcp4-tls", "udp4" + default: + return "tcp6-tls", "udp6" + } + } + return "tcp-tls", "udp" +} + // Init initialized necessary values for an ListenerConfig. func (lc *ListenerConfig) Init() { if lc.Policy != nil { @@ -333,6 +445,7 @@ func (lc *ListenerConfig) Init() { // ValidateConfig validates the given config. func ValidateConfig(validate *validator.Validate, cfg *Config) error { _ = validate.RegisterValidation("dnsrcode", validateDnsRcode) + _ = validate.RegisterValidation("ipstack", validateIpStack) _ = validate.RegisterValidation("iporempty", validateIpOrEmpty) return validate.Struct(cfg) } @@ -341,6 +454,15 @@ func validateDnsRcode(fl validator.FieldLevel) bool { return dnsrcode.FromString(fl.Field().String()) != -1 } +func validateIpStack(fl validator.FieldLevel) bool { + switch fl.Field().String() { + case IpStackBoth, IpStackV4, IpStackV6, IpStackSplit, "": + return true + default: + return false + } +} + func validateIpOrEmpty(fl validator.FieldLevel) bool { val := fl.Field().String() if val == "" { @@ -384,3 +506,7 @@ func ResolverTypeFromEndpoint(endpoint string) string { } return ResolverTypeDOT } + +func pick(s []string) string { + return s[rand.Intn(len(s))] +} diff --git a/config_internal_test.go b/config_internal_test.go index 4c67826..bf310f9 100644 --- a/config_internal_test.go +++ b/config_internal_test.go @@ -48,6 +48,7 @@ func TestUpstreamConfig_Init(t *testing.T) { BootstrapIP: "", Domain: "example.com", Timeout: 0, + IPStack: IpStackBoth, u: u1, }, }, @@ -68,6 +69,7 @@ func TestUpstreamConfig_Init(t *testing.T) { BootstrapIP: "", Domain: "example.com", Timeout: 0, + IPStack: IpStackBoth, u: u2, }, }, @@ -88,6 +90,7 @@ func TestUpstreamConfig_Init(t *testing.T) { BootstrapIP: "", Domain: "freedns.controld.com", Timeout: 0, + IPStack: IpStackSplit, }, }, { @@ -99,6 +102,7 @@ func TestUpstreamConfig_Init(t *testing.T) { BootstrapIP: "", Domain: "", Timeout: 0, + IPStack: IpStackSplit, }, &UpstreamConfig{ Name: "dot", @@ -107,6 +111,7 @@ func TestUpstreamConfig_Init(t *testing.T) { BootstrapIP: "", Domain: "freedns.controld.com", Timeout: 0, + IPStack: IpStackSplit, }, }, { @@ -126,6 +131,7 @@ func TestUpstreamConfig_Init(t *testing.T) { BootstrapIP: "1.2.3.4", Domain: "1.2.3.4", Timeout: 0, + IPStack: IpStackBoth, }, }, { @@ -145,6 +151,7 @@ func TestUpstreamConfig_Init(t *testing.T) { BootstrapIP: "1.2.3.4", Domain: "1.2.3.4", Timeout: 0, + IPStack: IpStackBoth, }, }, { @@ -157,6 +164,7 @@ func TestUpstreamConfig_Init(t *testing.T) { Domain: "", Timeout: 0, SendClientInfo: ptrBool(false), + IPStack: IpStackBoth, }, &UpstreamConfig{ Name: "doh", @@ -166,6 +174,7 @@ func TestUpstreamConfig_Init(t *testing.T) { Domain: "example.com", Timeout: 0, SendClientInfo: ptrBool(false), + IPStack: IpStackBoth, u: u2, }, }, diff --git a/config_quic.go b/config_quic.go index 9c6d668..8c0fb97 100644 --- a/config_quic.go +++ b/config_quic.go @@ -7,10 +7,15 @@ import ( "crypto/tls" "errors" "net" + "net/http" "sync" + "time" + "github.com/miekg/dns" "github.com/quic-go/quic-go" "github.com/quic-go/quic-go/http3" + + ctrldnet "github.com/Control-D-Inc/ctrld/internal/net" ) func (uc *UpstreamConfig) setupDOH3Transport() { @@ -18,9 +23,7 @@ func (uc *UpstreamConfig) setupDOH3Transport() { uc.pingUpstream() } -func (uc *UpstreamConfig) setupDOH3TransportWithoutPingUpstream() { - uc.mu.Lock() - defer uc.mu.Unlock() +func (uc *UpstreamConfig) newDOH3Transport(addrs []string) http.RoundTripper { rt := &http3.RoundTripper{} rt.TLSClientConfig = &tls.Config{RootCAs: uc.certPool} rt.Dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { @@ -40,20 +43,57 @@ func (uc *UpstreamConfig) setupDOH3TransportWithoutPingUpstream() { } return quic.DialEarlyContext(ctx, udpConn, remoteAddr, domain, tlsCfg, cfg) } - addrs := make([]string, len(uc.bootstrapIPs)) - for i := range uc.bootstrapIPs { - addrs[i] = net.JoinHostPort(uc.bootstrapIPs[i], port) + dialAddrs := make([]string, len(addrs)) + for i := range addrs { + dialAddrs[i] = net.JoinHostPort(addrs[i], port) } pd := &quicParallelDialer{} - conn, err := pd.Dial(ctx, domain, addrs, tlsCfg, cfg) + conn, err := pd.Dial(ctx, domain, dialAddrs, tlsCfg, cfg) if err != nil { return nil, err } ProxyLog.Debug().Msgf("sending doh3 request to: %s", conn.RemoteAddr()) return conn, err } + return rt +} - uc.http3RoundTripper = rt +func (uc *UpstreamConfig) setupDOH3TransportWithoutPingUpstream() { + uc.mu.Lock() + defer uc.mu.Unlock() + switch uc.IPStack { + case IpStackBoth, "": + uc.http3RoundTripper = uc.newDOH3Transport(uc.bootstrapIPs) + case IpStackV4: + uc.http3RoundTripper = uc.newDOH3Transport(uc.bootstrapIPs4) + case IpStackV6: + uc.http3RoundTripper = uc.newDOH3Transport(uc.bootstrapIPs6) + case IpStackSplit: + uc.http3RoundTripper4 = uc.newDOH3Transport(uc.bootstrapIPs4) + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + if ctrldnet.IPv6Available(ctx) { + uc.http3RoundTripper6 = uc.newDOH3Transport(uc.bootstrapIPs6) + } else { + uc.http3RoundTripper6 = uc.http3RoundTripper4 + } + uc.http3RoundTripper = uc.newDOH3Transport(uc.bootstrapIPs) + } +} + +func (uc *UpstreamConfig) doh3Transport(dnsType uint16) http.RoundTripper { + switch uc.IPStack { + case IpStackBoth, IpStackV4, IpStackV6: + return uc.http3RoundTripper + case IpStackSplit: + switch dnsType { + case dns.TypeA: + return uc.http3RoundTripper4 + default: + return uc.http3RoundTripper6 + } + } + return uc.http3RoundTripper } // Putting the code for quic parallel dialer here: diff --git a/config_quic_free.go b/config_quic_free.go index 3817e51..a4b1bdd 100644 --- a/config_quic_free.go +++ b/config_quic_free.go @@ -2,6 +2,9 @@ package ctrld +import "net/http" + func (uc *UpstreamConfig) setupDOH3Transport() {} -func (uc *UpstreamConfig) setupDOH3TransportWithoutPingUpstream() {} +func (uc *UpstreamConfig) setupDOH3TransportWithoutPingUpstream() {} +func (uc *UpstreamConfig) doh3Transport(dnsType uint16) http.RoundTripper { return nil } diff --git a/docs/config.md b/docs/config.md index e8cec53..fc78e98 100644 --- a/docs/config.md +++ b/docs/config.md @@ -227,9 +227,27 @@ Value `0` means no timeout. The protocol that `ctrld` will use to send DNS requests to upstream. - Type: string - - required: yes + - Required: yes - Valid values: `doh`, `doh3`, `dot`, `doq`, `legacy`, `os` +### ip_stack +Specifying what kind of ip stack that `ctrld` will use to connect to upstream. + + - Type: string + - Required: no + - Valid values: + - `both`: using either ipv4 or ipv6. + - `v4`: only dial upstream via IPv4, never dial IPv6. + - `v6`: only dial upstream via IPv6, never dial IPv4. + - `split`: + - If `A` record is requested -> dial via ipv4. + - If `AAAA` or any other record is requested -> dial ipv6 (if available, otherwise ipv4) + +If `ip_stack` is empty, or undefined: + + - Default value is `both` for non-Control D resolvers. + - Default value is `split` for Control D resolvers. + ## Network The `[network]` section defines networks from which DNS queries can originate from. These are used in policies. You can define multiple networks, and each one can have multiple cidrs. diff --git a/doh.go b/doh.go index 000cc49..e831feb 100644 --- a/doh.go +++ b/doh.go @@ -36,12 +36,12 @@ func newDohResolver(uc *UpstreamConfig) *dohResolver { } type dohResolver struct { + uc *UpstreamConfig endpoint *url.URL isDoH3 bool transport *http.Transport http3RoundTripper http.RoundTripper sendClientInfo bool - uc *UpstreamConfig } func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) { @@ -61,18 +61,22 @@ func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro return nil, fmt.Errorf("could not create request: %w", err) } addHeader(ctx, req, r.sendClientInfo) - - c := http.Client{Transport: r.transport} + dnsTyp := uint16(0) + if len(msg.Question) > 0 { + dnsTyp = msg.Question[0].Qtype + } + c := http.Client{Transport: r.uc.dohTransport(dnsTyp)} if r.isDoH3 { - if r.http3RoundTripper == nil { + transport := r.uc.doh3Transport(dnsTyp) + if transport == nil { return nil, errors.New("DoH3 is not supported") } - c.Transport = r.http3RoundTripper + c.Transport = transport } resp, err := c.Do(req) if err != nil { if r.isDoH3 { - if closer, ok := r.http3RoundTripper.(io.Closer); ok { + if closer, ok := c.Transport.(io.Closer); ok { closer.Close() } } diff --git a/doq.go b/doq.go index 20919e3..f0a7bc1 100644 --- a/doq.go +++ b/doq.go @@ -20,11 +20,17 @@ type doqResolver struct { func (r *doqResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) { endpoint := r.uc.Endpoint tlsConfig := &tls.Config{NextProtos: []string{"doq"}} - if r.uc.BootstrapIP != "" { - tlsConfig.ServerName = r.uc.Domain - _, port, _ := net.SplitHostPort(endpoint) - endpoint = net.JoinHostPort(r.uc.BootstrapIP, port) + ip := r.uc.BootstrapIP + if ip == "" { + dnsTyp := uint16(0) + if len(msg.Question) > 0 { + dnsTyp = msg.Question[0].Qtype + } + ip = r.uc.bootstrapIPForDNSType(dnsTyp) } + tlsConfig.ServerName = r.uc.Domain + _, port, _ := net.SplitHostPort(endpoint) + endpoint = net.JoinHostPort(ip, port) return resolve(ctx, msg, endpoint, tlsConfig) } diff --git a/dot.go b/dot.go index 11befb7..2e11a8f 100644 --- a/dot.go +++ b/dot.go @@ -14,13 +14,19 @@ type dotResolver struct { func (r *dotResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) { // The dialer is used to prevent bootstrapping cycle. - // If r.endpoing is set to dns.controld.dev, we need to resolve + // If r.endpoint is set to dns.controld.dev, we need to resolve // dns.controld.dev first. By using a dialer with custom resolver, // we ensure that we can always resolve the bootstrap domain // regardless of the machine DNS status. dialer := newDialer(net.JoinHostPort(bootstrapDNS, "53")) + dnsTyp := uint16(0) + if len(msg.Question) > 0 { + dnsTyp = msg.Question[0].Qtype + } + + tcpNet, _ := r.uc.netForDNSType(dnsTyp) dnsClient := &dns.Client{ - Net: "tcp-tls", + Net: tcpNet, Dialer: dialer, TLSConfig: &tls.Config{RootCAs: r.uc.certPool}, } diff --git a/resolver.go b/resolver.go index f2a8424..89b98b6 100644 --- a/resolver.go +++ b/resolver.go @@ -34,7 +34,7 @@ var errUnknownResolver = errors.New("unknown resolver") // NewResolver creates a Resolver based on the given upstream config. func NewResolver(uc *UpstreamConfig) (Resolver, error) { - typ, endpoint := uc.Type, uc.Endpoint + typ := uc.Type switch typ { case ResolverTypeDOH, ResolverTypeDOH3: return newDohResolver(uc), nil @@ -45,7 +45,7 @@ func NewResolver(uc *UpstreamConfig) (Resolver, error) { case ResolverTypeOS: return or, nil case ResolverTypeLegacy: - return &legacyResolver{endpoint: endpoint}, nil + return &legacyResolver{uc: uc}, nil } return nil, fmt.Errorf("%w: %s", errUnknownResolver, typ) } @@ -110,17 +110,22 @@ func newDialer(dnsAddress string) *net.Dialer { } type legacyResolver struct { - endpoint string + uc *UpstreamConfig } func (r *legacyResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) { // See comment in (*dotResolver).resolve method. dialer := newDialer(net.JoinHostPort(bootstrapDNS, "53")) + dnsTyp := uint16(0) + if len(msg.Question) > 0 { + dnsTyp = msg.Question[0].Qtype + } + _, udpNet := r.uc.netForDNSType(dnsTyp) dnsClient := &dns.Client{ - Net: "udp", + Net: udpNet, Dialer: dialer, } - answer, _, err := dnsClient.ExchangeContext(ctx, msg, r.endpoint) + answer, _, err := dnsClient.ExchangeContext(ctx, msg, r.uc.Endpoint) return answer, err } From 704bc27dba1630397d9d378c895968cac764945e Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Sat, 29 Apr 2023 13:21:57 +0700 Subject: [PATCH 34/55] Check msg is not nil before access Question field --- doq.go | 2 +- dot.go | 2 +- resolver.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doq.go b/doq.go index f0a7bc1..365fa10 100644 --- a/doq.go +++ b/doq.go @@ -23,7 +23,7 @@ func (r *doqResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro ip := r.uc.BootstrapIP if ip == "" { dnsTyp := uint16(0) - if len(msg.Question) > 0 { + if msg != nil && len(msg.Question) > 0 { dnsTyp = msg.Question[0].Qtype } ip = r.uc.bootstrapIPForDNSType(dnsTyp) diff --git a/dot.go b/dot.go index 2e11a8f..68cf2e1 100644 --- a/dot.go +++ b/dot.go @@ -20,7 +20,7 @@ func (r *dotResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro // regardless of the machine DNS status. dialer := newDialer(net.JoinHostPort(bootstrapDNS, "53")) dnsTyp := uint16(0) - if len(msg.Question) > 0 { + if msg != nil && len(msg.Question) > 0 { dnsTyp = msg.Question[0].Qtype } diff --git a/resolver.go b/resolver.go index 89b98b6..1e07c78 100644 --- a/resolver.go +++ b/resolver.go @@ -117,7 +117,7 @@ func (r *legacyResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, e // See comment in (*dotResolver).resolve method. dialer := newDialer(net.JoinHostPort(bootstrapDNS, "53")) dnsTyp := uint16(0) - if len(msg.Question) > 0 { + if msg != nil && len(msg.Question) > 0 { dnsTyp = msg.Question[0].Qtype } _, udpNet := r.uc.netForDNSType(dnsTyp) From 4c45e6cf3d6cd1b52b7001450c4d330d5176196f Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 1 May 2023 23:47:47 +0700 Subject: [PATCH 35/55] Lock while getting doh/doh3 transport --- config.go | 2 ++ config_quic.go | 2 ++ doh.go | 5 ----- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/config.go b/config.go index f3e28fc..454ffec 100644 --- a/config.go +++ b/config.go @@ -380,6 +380,8 @@ func (uc *UpstreamConfig) isControlD() bool { } func (uc *UpstreamConfig) dohTransport(dnsType uint16) http.RoundTripper { + uc.mu.Lock() + defer uc.mu.Unlock() switch uc.IPStack { case IpStackBoth, IpStackV4, IpStackV6: return uc.transport diff --git a/config_quic.go b/config_quic.go index 8c0fb97..ad695a8 100644 --- a/config_quic.go +++ b/config_quic.go @@ -82,6 +82,8 @@ func (uc *UpstreamConfig) setupDOH3TransportWithoutPingUpstream() { } func (uc *UpstreamConfig) doh3Transport(dnsType uint16) http.RoundTripper { + uc.mu.Lock() + defer uc.mu.Unlock() switch uc.IPStack { case IpStackBoth, IpStackV4, IpStackV6: return uc.http3RoundTripper diff --git a/doh.go b/doh.go index e831feb..155361e 100644 --- a/doh.go +++ b/doh.go @@ -21,13 +21,9 @@ const ( ) func newDohResolver(uc *UpstreamConfig) *dohResolver { - uc.mu.Lock() - transport := uc.transport - uc.mu.Unlock() r := &dohResolver{ endpoint: uc.u, isDoH3: uc.Type == ResolverTypeDOH3, - transport: transport, http3RoundTripper: uc.http3RoundTripper, sendClientInfo: uc.UpstreamSendClientInfo(), uc: uc, @@ -39,7 +35,6 @@ type dohResolver struct { uc *UpstreamConfig endpoint *url.URL isDoH3 bool - transport *http.Transport http3RoundTripper http.RoundTripper sendClientInfo bool } From 7a156d7d1560d0bf3bf384449777ce2eaa27001a Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 2 May 2023 01:03:34 +0700 Subject: [PATCH 36/55] Wait until bootstrap IPs resolved When bootstrapping, if the network changed, for example, firewall rules changed during VPN connection, the bootstrap IPs may not be resolved, so ctrld won't work. Since bootstrap IPs is necessary for ctrld to work properly, we should wait until we can resolve upstream IP before we can start serving requests. --- config.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/config.go b/config.go index 454ffec..a85cbd0 100644 --- a/config.go +++ b/config.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "crypto/x509" + "errors" "math/rand" "net" "net/http" @@ -17,6 +18,7 @@ import ( "github.com/miekg/dns" "github.com/spf13/viper" "golang.org/x/sync/singleflight" + "tailscale.com/logtail/backoff" "github.com/Control-D-Inc/ctrld/internal/dnsrcode" ctrldnet "github.com/Control-D-Inc/ctrld/internal/net" @@ -240,7 +242,15 @@ func (uc *UpstreamConfig) SetupBootstrapIP() { // SetupBootstrapIP manually find all available IPs of the upstream. // The first usable IP will be used as bootstrap IP of the upstream. func (uc *UpstreamConfig) setupBootstrapIP(withBootstrapDNS bool) { - uc.bootstrapIPs = lookupIP(uc.Domain, uc.Timeout, withBootstrapDNS) + b := backoff.NewBackoff("setupBootstrapIP", func(format string, args ...any) {}, 2*time.Second) + for { + uc.bootstrapIPs = lookupIP(uc.Domain, uc.Timeout, withBootstrapDNS) + if len(uc.bootstrapIPs) > 0 { + break + } + ProxyLog.Warn().Msg("could not resolve bootstrap IPs, retrying...") + b.BackOff(context.Background(), errors.New("no bootstrap IPs")) + } for _, ip := range uc.bootstrapIPs { if ctrldnet.IsIPv6(ip) { uc.bootstrapIPs6 = append(uc.bootstrapIPs6, ip) From 270ea9f6ca5ad3050e2b57f4a4beddf30b6ea544 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 2 May 2023 15:03:53 +0700 Subject: [PATCH 37/55] Do not block when ping upstream Because the network may not be available at the time ping upstream happens, so ctrld will stuck there waiting for pinging upstream. --- config.go | 2 +- config_quic.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config.go b/config.go index a85cbd0..bef32e3 100644 --- a/config.go +++ b/config.go @@ -297,7 +297,7 @@ func (uc *UpstreamConfig) SetupTransport() { func (uc *UpstreamConfig) setupDOHTransport() { uc.setupDOHTransportWithoutPingUpstream() - uc.pingUpstream() + go uc.pingUpstream() } func (uc *UpstreamConfig) newDOHTransport(addrs []string) *http.Transport { diff --git a/config_quic.go b/config_quic.go index ad695a8..b6861b6 100644 --- a/config_quic.go +++ b/config_quic.go @@ -20,7 +20,7 @@ import ( func (uc *UpstreamConfig) setupDOH3Transport() { uc.setupDOH3TransportWithoutPingUpstream() - uc.pingUpstream() + go uc.pingUpstream() } func (uc *UpstreamConfig) newDOH3Transport(addrs []string) http.RoundTripper { From 440d085c6d87c7ff9329bb5756c1be068eead09e Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 3 May 2023 17:57:36 +0700 Subject: [PATCH 38/55] cmd/ctrld: unified logging By using a separate console logging and use it in all places before reading in logging config. --- cmd/ctrld/cli.go | 150 +++++++++++++++++++++------------- cmd/ctrld/cli_router_linux.go | 8 +- cmd/ctrld/main.go | 31 +++++-- cmd/ctrld/service.go | 18 +--- 4 files changed, 122 insertions(+), 85 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index cad5b3d..4f1af63 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -7,7 +7,6 @@ import ( "encoding/base64" "errors" "fmt" - "log" "net" "net/netip" "os" @@ -75,6 +74,9 @@ var rootCmd = &cobra.Command{ Use: "ctrld", Short: strings.TrimLeft(rootShortDesc, "\n"), Version: curVersion(), + PreRun: func(cmd *cobra.Command, args []string) { + initConsoleLogging() + }, } func curVersion() string { @@ -106,9 +108,12 @@ func initCLI() { Use: "run", Short: "Run the DNS proxy server", Args: cobra.NoArgs, + PreRun: func(cmd *cobra.Command, args []string) { + initConsoleLogging() + }, Run: func(cmd *cobra.Command, args []string) { if daemon && runtime.GOOS == "windows" { - log.Fatal("Cannot run in daemon mode. Please install a Windows service.") + mainLog.Fatal().Msg("Cannot run in daemon mode. Please install a Windows service.") } waitCh := make(chan struct{}) @@ -152,7 +157,7 @@ func initCLI() { dir, err := userHomeDir() if err != nil { - log.Fatalf("failed to get config dir: %v", err) + mainLog.Fatal().Msgf("failed to get config dir: %v", err) } for _, config := range configs { ctrld.SetConfigNameWithPath(v, config.name, dir) @@ -165,16 +170,16 @@ func initCLI() { readBase64Config(configBase64) processNoConfigFlags(noConfigStart) if err := v.Unmarshal(&cfg); err != nil { - log.Fatalf("failed to unmarshal config: %v", err) + mainLog.Fatal().Msgf("failed to unmarshal config: %v", err) } - log.Printf("starting ctrld %s\n", curVersion()) + mainLog.Info().Msgf("starting ctrld %s", curVersion()) oi := osinfo.New() - log.Printf("os: %s\n", oi.String()) + mainLog.Info().Msgf("os: %s", oi.String()) // Wait for network up. if !ctrldnet.Up() { - log.Fatal("network is not up yet") + mainLog.Fatal().Msg("network is not up yet") } processLogAndCacheFlags() // Log config do not have thing to validate, so it's safe to init log here, @@ -182,7 +187,7 @@ func initCLI() { initLogging() processCDFlags() if err := ctrld.ValidateConfig(validator.New(), &cfg); err != nil { - log.Fatalf("invalid config: %v", err) + mainLog.Fatal().Msgf("invalid config: %v", err) } initCache() @@ -250,10 +255,13 @@ func initCLI() { rootCmd.AddCommand(runCmd) startCmd := &cobra.Command{ - PreRun: checkHasElevatedPrivilege, - Use: "start", - Short: "Install and start the ctrld service", - Args: cobra.NoArgs, + PreRun: func(cmd *cobra.Command, args []string) { + initConsoleLogging() + checkHasElevatedPrivilege() + }, + Use: "start", + Short: "Install and start the ctrld service", + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { sc := &service.Config{} *sc = *svcConfig @@ -264,7 +272,7 @@ func initCLI() { setDependencies(sc) sc.Arguments = append([]string{"run"}, osArgs...) if err := router.ConfigureService(sc); err != nil { - log.Fatal(err) + mainLog.Fatal().Err(err).Msg("failed to configure service on router") } // No config path, generating config in HOME directory. @@ -284,7 +292,7 @@ func initCLI() { readConfigFile(writeDefaultConfig && cdUID == "") if err := v.Unmarshal(&cfg); err != nil { - log.Fatalf("failed to unmarshal config: %v", err) + mainLog.Fatal().Msgf("failed to unmarshal config: %v", err) } logPath := cfg.Service.LogPath @@ -304,7 +312,7 @@ func initCLI() { prog := &prog{} s, err := service.New(prog, sc) if err != nil { - stderrMsg(err.Error()) + mainLog.Error().Msg(err.Error()) return } tasks := []task{ @@ -327,7 +335,7 @@ func initCLI() { status = selfCheckStatus(status) switch status { case service.StatusRunning: - mainLog.Info().Msg("Service started") + mainLog.Warn().Msg("Service started") default: mainLog.Error().Msg("Service did not start, please check system/service log for details error") if runtime.GOOS == "linux" { @@ -354,40 +362,46 @@ func initCLI() { _ = startCmd.Flags().MarkHidden("router") stopCmd := &cobra.Command{ - PreRun: checkHasElevatedPrivilege, - Use: "stop", - Short: "Stop the ctrld service", - Args: cobra.NoArgs, + PreRun: func(cmd *cobra.Command, args []string) { + initConsoleLogging() + checkHasElevatedPrivilege() + }, + Use: "stop", + Short: "Stop the ctrld service", + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { prog := &prog{} s, err := service.New(prog, svcConfig) if err != nil { - stderrMsg(err.Error()) + mainLog.Error().Msg(err.Error()) return } initLogging() if doTasks([]task{{s.Stop, true}}) { prog.resetDNS() - mainLog.Info().Msg("Service stopped") + mainLog.Warn().Msg("Service stopped") } }, } stopCmd.Flags().StringVarP(&iface, "iface", "", "", `Reset DNS setting for iface, "auto" means the default interface gateway`) restartCmd := &cobra.Command{ - PreRun: checkHasElevatedPrivilege, - Use: "restart", - Short: "Restart the ctrld service", - Args: cobra.NoArgs, + PreRun: func(cmd *cobra.Command, args []string) { + initConsoleLogging() + checkHasElevatedPrivilege() + }, + Use: "restart", + Short: "Restart the ctrld service", + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { s, err := service.New(&prog{}, svcConfig) if err != nil { - stderrMsg(err.Error()) + mainLog.Error().Msg(err.Error()) return } initLogging() if doTasks([]task{{s.Restart, true}}) { - stdoutMsg("Service restarted") + mainLog.Warn().Msg("Service restarted") } }, } @@ -396,39 +410,48 @@ func initCLI() { Use: "status", Short: "Show status of the ctrld service", Args: cobra.NoArgs, + PreRun: func(cmd *cobra.Command, args []string) { + initConsoleLogging() + }, Run: func(cmd *cobra.Command, args []string) { s, err := service.New(&prog{}, svcConfig) if err != nil { - stderrMsg(err.Error()) + mainLog.Error().Msg(err.Error()) return } status, err := serviceStatus(s) if err != nil { - stderrMsg(err.Error()) + mainLog.Error().Msg(err.Error()) os.Exit(1) } switch status { case service.StatusUnknown: - stdoutMsg("Unknown status") + mainLog.Warn().Msg("Unknown status") os.Exit(2) case service.StatusRunning: - stdoutMsg("Service is running") + mainLog.Warn().Msg("Service is running") os.Exit(0) case service.StatusStopped: - stdoutMsg("Service is stopped") + mainLog.Warn().Msg("Service is stopped") os.Exit(1) } }, } if runtime.GOOS == "darwin" { // On darwin, running status command without privileges may return wrong information. - statusCmd.PreRun = checkHasElevatedPrivilege + statusCmd.PreRun = func(cmd *cobra.Command, args []string) { + initConsoleLogging() + checkHasElevatedPrivilege() + } } uninstallCmd := &cobra.Command{ - PreRun: checkHasElevatedPrivilege, - Use: "uninstall", - Short: "Stop and uninstall the ctrld service", + PreRun: func(cmd *cobra.Command, args []string) { + initConsoleLogging() + checkHasElevatedPrivilege() + }, + Use: "uninstall", + Short: "Stop and uninstall the ctrld service", Long: `Stop and uninstall the ctrld service. NOTE: Uninstalling will set DNS to values provided by DHCP.`, @@ -437,7 +460,7 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`, prog := &prog{} s, err := service.New(prog, svcConfig) if err != nil { - stderrMsg(err.Error()) + mainLog.Error().Msg(err.Error()) return } tasks := []task{ @@ -454,7 +477,7 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`, if err := router.Cleanup(); err != nil { mainLog.Warn().Err(err).Msg("could not cleanup router") } - mainLog.Info().Msg("Service uninstalled") + mainLog.Warn().Msg("Service uninstalled") return } }, @@ -465,6 +488,9 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`, Use: "list", Short: "List network interfaces of the host", Args: cobra.NoArgs, + PreRun: func(cmd *cobra.Command, args []string) { + initConsoleLogging() + }, Run: func(cmd *cobra.Command, args []string) { err := interfaces.ForeachInterface(func(i interfaces.Interface, prefixes []netip.Prefix) { fmt.Printf("Index : %d\n", i.Index) @@ -487,7 +513,7 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`, println() }) if err != nil { - stderrMsg(err.Error()) + mainLog.Error().Msg(err.Error()) } }, } @@ -522,9 +548,12 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`, serviceCmd.AddCommand(interfacesCmd) rootCmd.AddCommand(serviceCmd) startCmdAlias := &cobra.Command{ - PreRun: checkHasElevatedPrivilege, - Use: "start", - Short: "Quick start service and configure DNS on interface", + PreRun: func(cmd *cobra.Command, args []string) { + initConsoleLogging() + checkHasElevatedPrivilege() + }, + Use: "start", + Short: "Quick start service and configure DNS on interface", Run: func(cmd *cobra.Command, args []string) { if !cmd.Flags().Changed("iface") { os.Args = append(os.Args, "--iface="+ifaceStartStop) @@ -537,9 +566,12 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`, startCmdAlias.Flags().AddFlagSet(startCmd.Flags()) rootCmd.AddCommand(startCmdAlias) stopCmdAlias := &cobra.Command{ - PreRun: checkHasElevatedPrivilege, - Use: "stop", - Short: "Quick stop service and remove DNS from interface", + PreRun: func(cmd *cobra.Command, args []string) { + initConsoleLogging() + checkHasElevatedPrivilege() + }, + Use: "stop", + Short: "Quick stop service and remove DNS from interface", Run: func(cmd *cobra.Command, args []string) { if !cmd.Flags().Changed("iface") { os.Args = append(os.Args, "--iface="+ifaceStartStop) @@ -583,7 +615,7 @@ func readConfigFile(writeDefaultConfig bool) bool { // If err == nil, there's a config supplied via `--config`, no default config written. err := v.ReadInConfig() if err == nil { - log.Println("loading config file from:", v.ConfigFileUsed()) + mainLog.Info().Msg("loading config file from: " + v.ConfigFileUsed()) defaultConfigFile = v.ConfigFileUsed() return true } @@ -595,22 +627,22 @@ func readConfigFile(writeDefaultConfig bool) bool { // If error is viper.ConfigFileNotFoundError, write default config. if _, ok := err.(viper.ConfigFileNotFoundError); ok { if err := v.Unmarshal(&cfg); err != nil { - log.Fatalf("failed to unmarshal default config: %v", err) + mainLog.Fatal().Msgf("failed to unmarshal default config: %v", err) } if err := writeConfigFile(); err != nil { - log.Fatalf("failed to write default config file: %v", err) + mainLog.Fatal().Msgf("failed to write default config file: %v", err) } else { fp, err := filepath.Abs(defaultConfigFile) if err != nil { - log.Fatalf("failed to get default config file path: %v", err) + mainLog.Fatal().Msgf("failed to get default config file path: %v", err) } - log.Println("writing default config file to: " + fp) + mainLog.Info().Msg("writing default config file to: " + fp) } defaultConfigWritten = true return false } // Otherwise, report fatal error and exit. - log.Fatalf("failed to decode config file: %v", err) + mainLog.Fatal().Msgf("failed to decode config file: %v", err) return false } @@ -620,10 +652,10 @@ func readBase64Config(configBase64 string) { } configStr, err := base64.StdEncoding.DecodeString(configBase64) if err != nil { - log.Fatalf("invalid base64 config: %v", err) + mainLog.Fatal().Msgf("invalid base64 config: %v", err) } if err := v.ReadConfig(bytes.NewReader(configStr)); err != nil { - log.Fatalf("failed to read base64 config: %v", err) + mainLog.Fatal().Msgf("failed to read base64 config: %v", err) } } @@ -632,7 +664,7 @@ func processNoConfigFlags(noConfigStart bool) { return } if listenAddress == "" || primaryUpstream == "" { - log.Fatal(`"listen" and "primary_upstream" flags must be set in no config mode`) + mainLog.Fatal().Msg(`"listen" and "primary_upstream" flags must be set in no config mode`) } processListenFlag() @@ -713,7 +745,7 @@ func processCDFlags() { logger.Info().Msg("using defined custom config of Control-D resolver") readBase64Config(resolverConfig.Ctrld.CustomConfig) if err := v.Unmarshal(&cfg); err != nil { - log.Fatalf("failed to unmarshal config: %v", err) + mainLog.Fatal().Msgf("failed to unmarshal config: %v", err) } } else { cfg = ctrld.Config{} @@ -757,11 +789,11 @@ func processListenFlag() { } host, portStr, err := net.SplitHostPort(listenAddress) if err != nil { - log.Fatalf("invalid listener address: %v", err) + mainLog.Fatal().Msgf("invalid listener address: %v", err) } port, err := strconv.Atoi(portStr) if err != nil { - log.Fatalf("invalid port number: %v", err) + mainLog.Fatal().Msgf("invalid port number: %v", err) } lc := &ctrld.ListenerConfig{ IP: host, @@ -828,7 +860,7 @@ func selfCheckStatus(status service.Status) service.Status { mu.Lock() defer mu.Unlock() if err := v.UnmarshalKey("listener", &lcChanged); err != nil { - log.Printf("failed to unmarshal listener config: %v", err) + mainLog.Error().Msgf("failed to unmarshal listener config: %v", err) return } }) diff --git a/cmd/ctrld/cli_router_linux.go b/cmd/ctrld/cli_router_linux.go index 46dfd5f..6352c91 100644 --- a/cmd/ctrld/cli_router_linux.go +++ b/cmd/ctrld/cli_router_linux.go @@ -1,7 +1,6 @@ package main import ( - "log" "os" "os/exec" "strings" @@ -27,6 +26,9 @@ func initRouterCLI() { routerCmd := &cobra.Command{ Use: "setup", Short: b.String(), + PreRun: func(cmd *cobra.Command, args []string) { + initConsoleLogging() + }, Run: func(cmd *cobra.Command, args []string) { if len(args) == 0 { _ = cmd.Help() @@ -47,7 +49,7 @@ func initRouterCLI() { } exe, err := os.Executable() if err != nil { - log.Fatal(err) + mainLog.Fatal().Msgf("could not find executable path: %v", err) os.Exit(1) } @@ -59,7 +61,7 @@ func initRouterCLI() { command.Stderr = os.Stderr command.Stdin = os.Stdin if err := command.Run(); err != nil { - log.Fatal(err) + mainLog.Fatal().Msg(err.Error()) } }, } diff --git a/cmd/ctrld/main.go b/cmd/ctrld/main.go index 793ca95..f3ecfb3 100644 --- a/cmd/ctrld/main.go +++ b/cmd/ctrld/main.go @@ -2,7 +2,6 @@ package main import ( "io" - "log" "os" "path/filepath" "time" @@ -31,7 +30,8 @@ var ( ifaceStartStop string setupRouter bool - mainLog = zerolog.New(io.Discard) + mainLog = zerolog.New(io.Discard) + consoleWriter zerolog.ConsoleWriter ) func main() { @@ -39,7 +39,7 @@ func main() { initCLI() initRouterCLI() if err := rootCmd.Execute(); err != nil { - stderrMsg(err.Error()) + mainLog.Error().Msg(err.Error()) os.Exit(1) } } @@ -58,28 +58,41 @@ func normalizeLogFilePath(logFilePath string) string { return filepath.Join(dir, logFilePath) } +func initConsoleLogging() { + consoleWriter = zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) { + w.TimeFormat = time.StampMilli + }) + multi := zerolog.MultiLevelWriter(consoleWriter) + mainLog = mainLog.Output(multi).With().Timestamp().Logger() + switch { + case verbose == 1: + zerolog.SetGlobalLevel(zerolog.InfoLevel) + case verbose > 1: + zerolog.SetGlobalLevel(zerolog.DebugLevel) + default: + zerolog.SetGlobalLevel(zerolog.WarnLevel) + } +} + func initLogging() { writers := []io.Writer{io.Discard} if logFilePath := normalizeLogFilePath(cfg.Service.LogPath); logFilePath != "" { // Create parent directory if necessary. if err := os.MkdirAll(filepath.Dir(logFilePath), 0750); err != nil { - log.Printf("failed to create log path: %v", err) + mainLog.Error().Msgf("failed to create log path: %v", err) os.Exit(1) } // Backup old log file with .1 suffix. if err := os.Rename(logFilePath, logFilePath+".1"); err != nil && !os.IsNotExist(err) { - log.Printf("could not backup old log file: %v", err) + mainLog.Error().Msgf("could not backup old log file: %v", err) } logFile, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_RDWR, os.FileMode(0o600)) if err != nil { - log.Printf("failed to create log file: %v", err) + mainLog.Error().Msgf("failed to create log file: %v", err) os.Exit(1) } writers = append(writers, logFile) } - consoleWriter := zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) { - w.TimeFormat = time.StampMilli - }) writers = append(writers, consoleWriter) multi := zerolog.MultiLevelWriter(writers...) mainLog = mainLog.Output(multi).With().Timestamp().Logger() diff --git a/cmd/ctrld/service.go b/cmd/ctrld/service.go index 9d56ec5..43d3261 100644 --- a/cmd/ctrld/service.go +++ b/cmd/ctrld/service.go @@ -3,22 +3,12 @@ package main import ( "bytes" "errors" - "fmt" "os" "os/exec" "github.com/kardianos/service" - "github.com/spf13/cobra" ) -func stderrMsg(msg string) { - _, _ = fmt.Fprintln(os.Stderr, msg) -} - -func stdoutMsg(msg string) { - _, _ = fmt.Fprintln(os.Stdout, msg) -} - type task struct { f func() error abortOnError bool @@ -29,7 +19,7 @@ func doTasks(tasks []task) bool { for _, task := range tasks { if err := task.f(); err != nil { if task.abortOnError { - stderrMsg(errors.Join(prevErr, err).Error()) + mainLog.Error().Msg(errors.Join(prevErr, err).Error()) return false } prevErr = err @@ -38,14 +28,14 @@ func doTasks(tasks []task) bool { return true } -func checkHasElevatedPrivilege(cmd *cobra.Command, args []string) { +func checkHasElevatedPrivilege() { ok, err := hasElevatedPrivilege() if err != nil { - fmt.Printf("could not detect user privilege: %v", err) + mainLog.Error().Msgf("could not detect user privilege: %v", err) return } if !ok { - fmt.Println("Please relaunch process with admin/root privilege.") + mainLog.Error().Msg("Please relaunch process with admin/root privilege.") os.Exit(1) } } From e3a792d50d77f6b31d145ca9cc8f9013d2488e0f Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 3 May 2023 18:12:40 +0700 Subject: [PATCH 39/55] cmd/ctrld: start listener with no default upstream We can have more listeners than upstreams. --- cmd/ctrld/dns_proxy.go | 3 +++ cmd/ctrld/prog.go | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/ctrld/dns_proxy.go b/cmd/ctrld/dns_proxy.go index f473634..af49bbf 100644 --- a/cmd/ctrld/dns_proxy.go +++ b/cmd/ctrld/dns_proxy.go @@ -257,6 +257,9 @@ func (p *prog) proxy(ctx context.Context, upstreams []string, failoverRcodes []i return answer } for n, upstreamConfig := range upstreamConfigs { + if upstreamConfig == nil { + continue + } answer := resolve(n, upstreamConfig, msg) if answer == nil { if serveStaleCache && staleAnswer != nil { diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index a170b36..f19aeda 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -91,8 +91,7 @@ func (p *prog) run() { listenerConfig := p.cfg.Listener[listenerNum] upstreamConfig := p.cfg.Upstream[listenerNum] if upstreamConfig == nil { - mainLog.Error().Msgf("missing upstream config for: [listener.%s]", listenerNum) - return + mainLog.Warn().Msgf("no default upstream for: [listener.%s]", listenerNum) } addr := net.JoinHostPort(listenerConfig.IP, strconv.Itoa(listenerConfig.Port)) mainLog.Info().Msgf("Starting DNS server on listener.%s: %s", listenerNum, addr) From 24100c4cbee5a4115a9989d1234c1cdfd124df6d Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 4 May 2023 00:05:52 +0700 Subject: [PATCH 40/55] cmd/ctrld: use Windscribe fork of zerolog For supporting default log level notice. While at it, also fix a missing os.Exit call when setup router on non-supported platforms. --- cmd/ctrld/cli.go | 16 ++++++++-------- cmd/ctrld/cli_router_linux.go | 1 + cmd/ctrld/main.go | 4 ++-- go.mod | 6 +++++- go.sum | 10 +++++----- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 4f1af63..a5ca76f 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -335,7 +335,7 @@ func initCLI() { status = selfCheckStatus(status) switch status { case service.StatusRunning: - mainLog.Warn().Msg("Service started") + mainLog.Notice().Msg("Service started") default: mainLog.Error().Msg("Service did not start, please check system/service log for details error") if runtime.GOOS == "linux" { @@ -379,7 +379,7 @@ func initCLI() { initLogging() if doTasks([]task{{s.Stop, true}}) { prog.resetDNS() - mainLog.Warn().Msg("Service stopped") + mainLog.Notice().Msg("Service stopped") } }, } @@ -401,7 +401,7 @@ func initCLI() { } initLogging() if doTasks([]task{{s.Restart, true}}) { - mainLog.Warn().Msg("Service restarted") + mainLog.Notice().Msg("Service restarted") } }, } @@ -426,13 +426,13 @@ func initCLI() { } switch status { case service.StatusUnknown: - mainLog.Warn().Msg("Unknown status") + mainLog.Notice().Msg("Unknown status") os.Exit(2) case service.StatusRunning: - mainLog.Warn().Msg("Service is running") + mainLog.Notice().Msg("Service is running") os.Exit(0) case service.StatusStopped: - mainLog.Warn().Msg("Service is stopped") + mainLog.Notice().Msg("Service is stopped") os.Exit(1) } }, @@ -477,7 +477,7 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`, if err := router.Cleanup(); err != nil { mainLog.Warn().Err(err).Msg("could not cleanup router") } - mainLog.Warn().Msg("Service uninstalled") + mainLog.Notice().Msg("Service uninstalled") return } }, @@ -887,7 +887,7 @@ func selfCheckStatus(status service.Status) service.Status { } func unsupportedPlatformHelp(cmd *cobra.Command) { - cmd.PrintErrln("Unsupported or incorrectly chosen router platform. Please open an issue and provide all relevant information: https://github.com/Control-D-Inc/ctrld/issues/new") + mainLog.Error().Msg("Unsupported or incorrectly chosen router platform. Please open an issue and provide all relevant information: https://github.com/Control-D-Inc/ctrld/issues/new") } func userHomeDir() (string, error) { diff --git a/cmd/ctrld/cli_router_linux.go b/cmd/ctrld/cli_router_linux.go index 6352c91..3bd70f1 100644 --- a/cmd/ctrld/cli_router_linux.go +++ b/cmd/ctrld/cli_router_linux.go @@ -46,6 +46,7 @@ func initRouterCLI() { case router.DDWrt, router.Merlin, router.OpenWrt, router.Ubios: default: unsupportedPlatformHelp(cmd) + os.Exit(1) } exe, err := os.Executable() if err != nil { diff --git a/cmd/ctrld/main.go b/cmd/ctrld/main.go index f3ecfb3..4f4c206 100644 --- a/cmd/ctrld/main.go +++ b/cmd/ctrld/main.go @@ -70,7 +70,7 @@ func initConsoleLogging() { case verbose > 1: zerolog.SetGlobalLevel(zerolog.DebugLevel) default: - zerolog.SetGlobalLevel(zerolog.WarnLevel) + zerolog.SetGlobalLevel(zerolog.NoticeLevel) } } @@ -99,7 +99,7 @@ func initLogging() { // TODO: find a better way. ctrld.ProxyLog = mainLog - zerolog.SetGlobalLevel(zerolog.WarnLevel) + zerolog.SetGlobalLevel(zerolog.NoticeLevel) logLevel := cfg.Service.LogLevel switch { case verbose == 1: diff --git a/go.mod b/go.mod index 1d1788c..169dc34 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Control-D-Inc/ctrld go 1.20 require ( - github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 + github.com/coreos/go-systemd/v22 v22.5.0 github.com/cuonglm/osinfo v0.0.0-20230329055532-c513f836da19 github.com/frankban/quicktest v1.14.3 github.com/fsnotify/fsnotify v1.6.0 @@ -77,3 +77,7 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/mr-karan/doggo => github.com/Windscribe/doggo v0.0.0-20220919152748-2c118fc391f8 + +replace github.com/rs/zerolog => github.com/Windscribe/zerolog v0.0.0-20230503170159-e6aa153233be diff --git a/go.sum b/go.sum index 6aedff1..3b884b7 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Windscribe/zerolog v0.0.0-20230503170159-e6aa153233be h1:qBKVRi7Mom5heOkyZ+NCIu9HZBiNCsRqrRe5t9pooik= +github.com/Windscribe/zerolog v0.0.0-20230503170159-e6aa153233be/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -50,8 +52,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 h1:rtAn27wIbmOGUs7RIbVgPEjb31ehTVniDwPGXyMxm5U= -github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cuonglm/osinfo v0.0.0-20230329055532-c513f836da19 h1:7P/f19Mr0oa3ug8BYt4JuRe/Zq3dF4Mrr4m8+Kw+Hcs= @@ -247,9 +249,7 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 h1:Ha8xCaq6ln1a+R91Km45Oq6lPXj2Mla6CRJYcuV2h1w= github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= -github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY= -github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= From ad4ca3287303c8d504f4a84359e7ccba0d98e306 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 4 May 2023 00:53:40 +0700 Subject: [PATCH 41/55] cmd/ctrld: factor out code to read config file So start/run command will use the same code path, prevent mismatch from reading/searching/writing config file. --- cmd/ctrld/cli.go | 48 ++++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index a5ca76f..cd9cc6c 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -146,26 +146,7 @@ func initCLI() { } noConfigStart := isNoConfigStart(cmd) writeDefaultConfig := !noConfigStart && configBase64 == "" - configs := []struct { - name string - written bool - }{ - // For compatibility, we check for config.toml first, but only read it if exists. - {"config", false}, - {"ctrld", writeDefaultConfig}, - } - - dir, err := userHomeDir() - if err != nil { - mainLog.Fatal().Msgf("failed to get config dir: %v", err) - } - for _, config := range configs { - ctrld.SetConfigNameWithPath(v, config.name, dir) - v.SetConfigFile(configPath) - if readConfigFile(config.written) { - break - } - } + tryReadingConfig(writeDefaultConfig) readBase64Config(configBase64) processNoConfigFlags(noConfigStart) @@ -285,12 +266,12 @@ func initCLI() { setWorkingDirectory(sc, dir) if configPath == "" && writeDefaultConfig { defaultConfigFile = filepath.Join(dir, defaultConfigFile) - v.SetConfigFile(defaultConfigFile) } sc.Arguments = append(sc.Arguments, "--homedir="+dir) } - readConfigFile(writeDefaultConfig && cdUID == "") + tryReadingConfig(writeDefaultConfig) + if err := v.Unmarshal(&cfg); err != nil { mainLog.Fatal().Msgf("failed to unmarshal config: %v", err) } @@ -909,3 +890,26 @@ func userHomeDir() (string, error) { } return dir, nil } + +func tryReadingConfig(writeDefaultConfig bool) { + configs := []struct { + name string + written bool + }{ + // For compatibility, we check for config.toml first, but only read it if exists. + {"config", false}, + {"ctrld", writeDefaultConfig}, + } + + dir, err := userHomeDir() + if err != nil { + mainLog.Fatal().Msgf("failed to get config dir: %v", err) + } + for _, config := range configs { + ctrld.SetConfigNameWithPath(v, config.name, dir) + v.SetConfigFile(configPath) + if readConfigFile(config.written) { + break + } + } +} From e0ae0f8e7baf472ccf887ae2ad04d65119beaa9b Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 4 May 2023 12:04:58 +0700 Subject: [PATCH 42/55] cmd/ctrld: set default value for ip/port from custom config if missing --- cmd/ctrld/cli.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index cd9cc6c..98ca358 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -728,6 +728,14 @@ func processCDFlags() { if err := v.Unmarshal(&cfg); err != nil { mainLog.Fatal().Msgf("failed to unmarshal config: %v", err) } + for _, listener := range cfg.Listener { + if listener.IP == "" { + listener.IP = randomLocalIP() + } + if listener.Port == 0 { + listener.Port = 53 + } + } } else { cfg = ctrld.Config{} cfg.Network = make(map[string]*ctrld.NetworkConfig) From c872a3b3f665efc3d4e5ffe07ad5f05d2274c867 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 4 May 2023 12:31:22 +0700 Subject: [PATCH 43/55] cmd/ctrld: add "--silent" to disable log output --- cmd/ctrld/cli.go | 7 +++++++ cmd/ctrld/main.go | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 98ca358..2a6872f 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -101,6 +101,13 @@ func initCLI() { "v", `verbose log output, "-v" basic logging, "-vv" debug level logging`, ) + rootCmd.PersistentFlags().BoolVarP( + &silent, + "silent", + "s", + false, + `do not write any log output`, + ) rootCmd.SetHelpCommand(&cobra.Command{Hidden: true}) rootCmd.CompletionOptions.HiddenDefaultCmd = true diff --git a/cmd/ctrld/main.go b/cmd/ctrld/main.go index 4f4c206..9903a0b 100644 --- a/cmd/ctrld/main.go +++ b/cmd/ctrld/main.go @@ -25,6 +25,7 @@ var ( cacheSize int cfg ctrld.Config verbose int + silent bool cdUID string iface string ifaceStartStop string @@ -65,6 +66,8 @@ func initConsoleLogging() { multi := zerolog.MultiLevelWriter(consoleWriter) mainLog = mainLog.Output(multi).With().Timestamp().Logger() switch { + case silent: + zerolog.SetGlobalLevel(zerolog.NoLevel) case verbose == 1: zerolog.SetGlobalLevel(zerolog.InfoLevel) case verbose > 1: @@ -102,6 +105,9 @@ func initLogging() { zerolog.SetGlobalLevel(zerolog.NoticeLevel) logLevel := cfg.Service.LogLevel switch { + case silent: + zerolog.SetGlobalLevel(zerolog.NoLevel) + return case verbose == 1: logLevel = "info" case verbose > 1: From be497a68dea3e86e278d1a11c0e556d37be13913 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 5 May 2023 08:29:41 +0700 Subject: [PATCH 44/55] internal/router: skip bad entry in leases file Seen in UDM Dream Machine. --- internal/router/client_info.go | 20 +++++++++++- internal/router/client_info_test.go | 47 ++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/internal/router/client_info.go b/internal/router/client_info.go index dff4ede..f6a13fb 100644 --- a/internal/router/client_info.go +++ b/internal/router/client_info.go @@ -2,6 +2,7 @@ package router import ( "bytes" + "io" "log" "net" "os" @@ -78,10 +79,27 @@ func GetClientInfoByMac(mac string) *ctrld.ClientInfo { } func readClientInfoFile(name string) error { + f, err := os.Open(name) + if err != nil { + return err + } + defer f.Close() + return readClientInfoReader(f) + +} + +func readClientInfoReader(reader io.Reader) error { r := routerPlatform.Load() - return lineread.File(name, func(line []byte) error { + return lineread.Reader(reader, func(line []byte) error { fields := bytes.Fields(line) + if len(fields) != 5 { + return nil + } mac := string(fields[1]) + if _, err := net.ParseMAC(mac); err != nil { + // The second field is not a mac, skip. + return nil + } ip := normalizeIP(string(fields[2])) if net.ParseIP(ip) == nil { log.Printf("invalid ip address entry: %q", ip) diff --git a/internal/router/client_info_test.go b/internal/router/client_info_test.go index 13b0648..2b228c2 100644 --- a/internal/router/client_info_test.go +++ b/internal/router/client_info_test.go @@ -1,6 +1,11 @@ package router -import "testing" +import ( + "strings" + "testing" + + "github.com/Control-D-Inc/ctrld" +) func Test_normalizeIP(t *testing.T) { tests := []struct { @@ -23,3 +28,43 @@ func Test_normalizeIP(t *testing.T) { }) } } + +func Test_readClientInfoReader(t *testing.T) { + tests := []struct { + name string + in string + mac string + }{ + { + "good", + `1683329857 e6:20:59:b8:c1:6d 192.168.1.186 * 01:e6:20:59:b8:c1:6d +`, + "e6:20:59:b8:c1:6d", + }, + { + "bad seen on UDMdream machine", + `1683329857 e6:20:59:b8:c1:6e 192.168.1.111 * 01:e6:20:59:b8:c1:6e +duid 00:01:00:01:2b:e4:2e:2c:52:52:14:26:dc:1c +1683322985 117442354 2600:4040:b0e6:b700::111 ASDASD 00:01:00:01:2a:d0:b9:81:00:07:32:4c:1c:07 +`, + "e6:20:59:b8:c1:6e", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + r := routerPlatform.Load() + r.mac.Delete(tc.mac) + if err := readClientInfoReader(strings.NewReader(tc.in)); err != nil { + t.Errorf("readClientInfoReader() error = %v", err) + } + info, existed := r.mac.Load(tc.mac) + if !existed { + t.Error("client info missing") + } + if ci, ok := info.(*ctrld.ClientInfo); ok && existed && ci.Mac != tc.mac { + t.Errorf("mac mismatched, got: %q, want: %q", ci.Mac, tc.mac) + } + }) + } +} From 5b6a3a4c6f2a69dcc75756d9358b19fd62486c3c Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 5 May 2023 21:25:54 +0700 Subject: [PATCH 45/55] internal/router: disable native dot on merlin While at it, also ensure custom config is ignored when running on router, because we need to point to 127.0.0.1:53 (dnsmasq listener). --- cmd/ctrld/cli.go | 7 ++++ internal/router/ddwrt.go | 64 ++++------------------------- internal/router/merlin.go | 9 +++++ internal/router/nvram.go | 84 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 57 deletions(-) create mode 100644 internal/router/nvram.go diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 2a6872f..2a12c12 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -743,6 +743,13 @@ func processCDFlags() { listener.Port = 53 } } + // On router, we want to keep the listener address point to dnsmasq listener, aka 127.0.0.1:53. + if router.Name() != "" { + if lc := cfg.Listener["0"]; lc != nil { + lc.IP = "127.0.0.1" + lc.Port = 53 + } + } } else { cfg = ctrld.Config{} cfg.Network = make(map[string]*ctrld.NetworkConfig) diff --git a/internal/router/ddwrt.go b/internal/router/ddwrt.go index ad6e921..52a5732 100644 --- a/internal/router/ddwrt.go +++ b/internal/router/ddwrt.go @@ -1,11 +1,9 @@ package router import ( - "bytes" "errors" "fmt" "os/exec" - "strings" ) const ( @@ -19,12 +17,6 @@ var ddwrtJffs2NotEnabledErr = errors.New(`could not install service without jffs https://wiki.dd-wrt.com/wiki/index.php/Journalling_Flash_File_System `) -var nvramKeys = map[string]string{ - "dns_dnsmasq": "1", // Make dnsmasq running but disable DNS ability, ctrld will replace it. - "dnsmasq_options": "", // Configuration of dnsmasq set by ctrld, filled by setupDDWrt. - "dns_crypt": "0", // Disable DNSCrypt. -} - func setupDDWrt() error { // Already setup. if val, _ := nvram("get", nvramCtrldSetupKey); val == "1" { @@ -35,28 +27,13 @@ func setupDDWrt() error { if err != nil { return err } - nvramKeys["dnsmasq_options"] = data - // Backup current value, store ctrld's configs. - for key, value := range nvramKeys { - old, err := nvram("get", key) - if err != nil { - return fmt.Errorf("%s: %w", old, err) - } - if out, err := nvram("set", nvramCtrldKeyPrefix+key+"="+old); err != nil { - return fmt.Errorf("%s: %w", out, err) - } - if out, err := nvram("set", key+"="+value); err != nil { - return fmt.Errorf("%s: %w", out, err) - } + + nvramKvMap := nvramKV() + nvramKvMap["dnsmasq_options"] = data + if err := nvramSetup(nvramKvMap); err != nil { + return err } - if out, err := nvram("set", nvramCtrldSetupKey+"=1"); err != nil { - return fmt.Errorf("%s: %w", out, err) - } - // Commit. - if out, err := nvram("commit"); err != nil { - return fmt.Errorf("%s: %w", out, err) - } // Restart dnsmasq service. if err := ddwrtRestartDNSMasq(); err != nil { return err @@ -66,24 +43,8 @@ func setupDDWrt() error { func cleanupDDWrt() error { // Restore old configs. - for key := range nvramKeys { - ctrldKey := nvramCtrldKeyPrefix + key - old, err := nvram("get", ctrldKey) - if err != nil { - return fmt.Errorf("%s: %w", old, err) - } - _, _ = nvram("unset", ctrldKey) - if out, err := nvram("set", key+"="+old); err != nil { - return fmt.Errorf("%s: %w", out, err) - } - } - - if out, err := nvram("unset", "ctrld_setup"); err != nil { - return fmt.Errorf("%s: %w", out, err) - } - // Commit. - if out, err := nvram("commit"); err != nil { - return fmt.Errorf("%s: %w", out, err) + if err := nvramRestore(nvramKV()); err != nil { + return err } // Restart dnsmasq service. if err := ddwrtRestartDNSMasq(); err != nil { @@ -96,17 +57,6 @@ func postInstallDDWrt() error { return nil } -func nvram(args ...string) (string, error) { - cmd := exec.Command("nvram", args...) - var stdout, stderr bytes.Buffer - cmd.Stdout = &stdout - cmd.Stderr = &stderr - if err := cmd.Run(); err != nil { - return "", fmt.Errorf("%s:%w", stderr.String(), err) - } - return strings.TrimSpace(stdout.String()), nil -} - func ddwrtRestartDNSMasq() error { if out, err := exec.Command("restart_dns").CombinedOutput(); err != nil { return fmt.Errorf("restart_dns: %s, %w", string(out), err) diff --git a/internal/router/merlin.go b/internal/router/merlin.go index b2386bf..ca739cb 100644 --- a/internal/router/merlin.go +++ b/internal/router/merlin.go @@ -38,10 +38,19 @@ func setupMerlin() error { if err := merlinRestartDNSMasq(); err != nil { return err } + + if err := nvramSetup(nvramKV()); err != nil { + return err + } + return nil } func cleanupMerlin() error { + // Restore old configs. + if err := nvramRestore(nvramKV()); err != nil { + return err + } buf, err := os.ReadFile(merlinDNSMasqPostConfPath) if err != nil && !os.IsNotExist(err) { return err diff --git a/internal/router/nvram.go b/internal/router/nvram.go new file mode 100644 index 0000000..79a7657 --- /dev/null +++ b/internal/router/nvram.go @@ -0,0 +1,84 @@ +package router + +import ( + "bytes" + "fmt" + "os/exec" + "strings" +) + +func nvram(args ...string) (string, error) { + cmd := exec.Command("nvram", args...) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + return "", fmt.Errorf("%s:%w", stderr.String(), err) + } + return strings.TrimSpace(stdout.String()), nil +} + +func nvramKV() map[string]string { + switch Name() { + case DDWrt: + return map[string]string{ + "dns_dnsmasq": "1", // Make dnsmasq running but disable DNS ability, ctrld will replace it. + "dnsmasq_options": "", // Configuration of dnsmasq set by ctrld, filled by setupDDWrt. + "dns_crypt": "0", // Disable DNSCrypt. + } + case Merlin: + return map[string]string{ + "dnspriv_enable": "0", // Ensure Merlin native DoT disabled. + } + } + return nil +} + +func nvramSetup(m map[string]string) error { + // Backup current value, store ctrld's configs. + for key, value := range m { + old, err := nvram("get", key) + if err != nil { + return fmt.Errorf("%s: %w", old, err) + } + if out, err := nvram("set", nvramCtrldKeyPrefix+key+"="+old); err != nil { + return fmt.Errorf("%s: %w", out, err) + } + if out, err := nvram("set", key+"="+value); err != nil { + return fmt.Errorf("%s: %w", out, err) + } + } + + if out, err := nvram("set", nvramCtrldSetupKey+"=1"); err != nil { + return fmt.Errorf("%s: %w", out, err) + } + // Commit. + if out, err := nvram("commit"); err != nil { + return fmt.Errorf("%s: %w", out, err) + } + return nil +} + +func nvramRestore(m map[string]string) error { + // Restore old configs. + for key := range m { + ctrldKey := nvramCtrldKeyPrefix + key + old, err := nvram("get", ctrldKey) + if err != nil { + return fmt.Errorf("%s: %w", old, err) + } + _, _ = nvram("unset", ctrldKey) + if out, err := nvram("set", key+"="+old); err != nil { + return fmt.Errorf("%s: %w", out, err) + } + } + + if out, err := nvram("unset", "ctrld_setup"); err != nil { + return fmt.Errorf("%s: %w", out, err) + } + // Commit. + if out, err := nvram("commit"); err != nil { + return fmt.Errorf("%s: %w", out, err) + } + return nil +} From 521f06dcc10e61ddccc41a9798d05aa38d674f14 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 5 May 2023 22:38:04 +0700 Subject: [PATCH 46/55] cmd/ctrld: force 127.0.0.1:53 for listener.0 only --- cmd/ctrld/dns_proxy.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/ctrld/dns_proxy.go b/cmd/ctrld/dns_proxy.go index af49bbf..3ce2af7 100644 --- a/cmd/ctrld/dns_proxy.go +++ b/cmd/ctrld/dns_proxy.go @@ -88,7 +88,7 @@ func (p *prog) serveDNS(listenerNum string) error { }) } g.Go(func() error { - s, errCh := runDNSServer(dnsListenAddress(listenerConfig), proto, handler) + s, errCh := runDNSServer(dnsListenAddress(listenerNum, listenerConfig), proto, handler) defer s.Shutdown() if listenerConfig.Port == 0 { switch s.Net { @@ -397,8 +397,8 @@ func needLocalIPv6Listener() bool { return ctrldnet.SupportsIPv6ListenLocal() && runtime.GOOS == "windows" } -func dnsListenAddress(lc *ctrld.ListenerConfig) string { - if addr := router.ListenAddress(); addr != "" { +func dnsListenAddress(lcNum string, lc *ctrld.ListenerConfig) string { + if addr := router.ListenAddress(); addr != "" && lcNum == "0" { return addr } return net.JoinHostPort(lc.IP, strconv.Itoa(lc.Port)) From 45895067c65563f17471157182a1d4ded8aa81da Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 5 May 2023 23:37:02 +0700 Subject: [PATCH 47/55] cmd/ctrld: only ignore listener.0 setup when setup router --- cmd/ctrld/dns_proxy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/ctrld/dns_proxy.go b/cmd/ctrld/dns_proxy.go index 3ce2af7..ac9af50 100644 --- a/cmd/ctrld/dns_proxy.go +++ b/cmd/ctrld/dns_proxy.go @@ -398,7 +398,7 @@ func needLocalIPv6Listener() bool { } func dnsListenAddress(lcNum string, lc *ctrld.ListenerConfig) string { - if addr := router.ListenAddress(); addr != "" && lcNum == "0" { + if addr := router.ListenAddress(); setupRouter && addr != "" && lcNum == "0" { return addr } return net.JoinHostPort(lc.IP, strconv.Itoa(lc.Port)) From d75f871541c90be2660985fb8663a4a9df7e3e94 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 8 May 2023 22:54:05 +0700 Subject: [PATCH 48/55] internal/router: workaround problem with ntp bug on some Merlin routers On some Merlin routers, due to ntp bug, after rebooing, dnsmasq config was restored to default without ctrld changes, causing ctrld stop working. Workaround this problem by catching restart diskmon event, which is triggered by ntpd_synced, then restart dnsmasq. --- internal/router/service_merlin.go | 81 ++++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 17 deletions(-) diff --git a/internal/router/service_merlin.go b/internal/router/service_merlin.go index 7f40b0d..3878c71 100644 --- a/internal/router/service_merlin.go +++ b/internal/router/service_merlin.go @@ -15,7 +15,10 @@ import ( "github.com/kardianos/service" ) -const merlinJFFSScriptPath = "/jffs/scripts/services-start" +const ( + merlinJFFSScriptPath = "/jffs/scripts/services-start" + merlinJFFSServiceEventScriptPath = "/jffs/scripts/service-event" +) type merlinSvc struct { i service.Interface @@ -101,14 +104,6 @@ func (s *merlinSvc) Install() error { if err := os.MkdirAll(filepath.Dir(merlinJFFSScriptPath), 0755); err != nil { return fmt.Errorf("os.MkdirAll: %w", err) } - if _, err := os.Stat(merlinJFFSScriptPath); os.IsNotExist(err) { - if err := os.WriteFile(merlinJFFSScriptPath, []byte("#!/bin/sh\n"), 0755); err != nil { - return err - } - } - if err := os.Chmod(merlinJFFSScriptPath, 0755); err != nil { - return fmt.Errorf("os.Chmod: jffs script: %w", err) - } tmpScript, err := os.CreateTemp("", "ctrld_install") if err != nil { @@ -117,14 +112,35 @@ func (s *merlinSvc) Install() error { defer os.Remove(tmpScript.Name()) defer tmpScript.Close() - if _, err := tmpScript.WriteString(merlinAddStartupScript); err != nil { + if _, err := tmpScript.WriteString(merlinAddLineToScript); err != nil { return fmt.Errorf("tmpScript.WriteString: %w", err) } if err := tmpScript.Close(); err != nil { return fmt.Errorf("tmpScript.Close: %w", err) } - if err := exec.Command("sh", tmpScript.Name(), s.configPath()+" start", merlinJFFSScriptPath).Run(); err != nil { - return fmt.Errorf("exec.Command: add startup script: %w", err) + addLineToScript := func(line, script string) error { + if _, err := os.Stat(script); os.IsNotExist(err) { + if err := os.WriteFile(script, []byte("#!/bin/sh\n"), 0755); err != nil { + return err + } + } + if err := os.Chmod(script, 0755); err != nil { + return fmt.Errorf("os.Chmod: jffs script: %w", err) + } + + if err := exec.Command("sh", tmpScript.Name(), line, script).Run(); err != nil { + return fmt.Errorf("exec.Command: add startup script: %w", err) + } + return nil + } + + for script, line := range map[string]string{ + merlinJFFSScriptPath: s.configPath() + " start", + merlinJFFSServiceEventScriptPath: s.configPath() + ` service_event "$1" "$2"`, + } { + if err := addLineToScript(line, script); err != nil { + return err + } } return nil @@ -141,15 +157,37 @@ func (s *merlinSvc) Uninstall() error { defer os.Remove(tmpScript.Name()) defer tmpScript.Close() - if _, err := tmpScript.WriteString(merlinRemoveStartupScript); err != nil { + if _, err := tmpScript.WriteString(merlinRemoveLineFromScript); err != nil { return fmt.Errorf("tmpScript.WriteString: %w", err) } if err := tmpScript.Close(); err != nil { return fmt.Errorf("tmpScript.Close: %w", err) } - if err := exec.Command("sh", tmpScript.Name(), s.configPath()+" start", merlinJFFSScriptPath).Run(); err != nil { - return fmt.Errorf("exec.Command: %w", err) + removeLineFromScript := func(line, script string) error { + if _, err := os.Stat(script); os.IsNotExist(err) { + if err := os.WriteFile(script, []byte("#!/bin/sh\n"), 0755); err != nil { + return err + } + } + if err := os.Chmod(script, 0755); err != nil { + return fmt.Errorf("os.Chmod: jffs script: %w", err) + } + + if err := exec.Command("sh", tmpScript.Name(), line, script).Run(); err != nil { + return fmt.Errorf("exec.Command: add startup script: %w", err) + } + return nil } + + for script, line := range map[string]string{ + merlinJFFSScriptPath: s.configPath() + " start", + merlinJFFSServiceEventScriptPath: s.configPath() + ` service_event "$1" "$2"`, + } { + if err := removeLineFromScript(line, script); err != nil { + return err + } + } + return nil } @@ -278,6 +316,15 @@ case "$1" in exit 1 fi ;; + service_event) + event=$2 + svc=$3 + dnsmasq_pid_file=$(sed -n '/pid-file=/s///p' /etc/dnsmasq.conf) + + if [ "$event" = "restart" ] && [ "$svc" = "diskmon" ]; then + kill "$(cat "$dnsmasq_pid_file")" >/dev/null 2>&1 + fi + ;; *) echo "Usage: $0 {start|stop|restart|status}" exit 1 @@ -286,7 +333,7 @@ esac exit 0 ` -const merlinAddStartupScript = `#!/bin/sh +const merlinAddLineToScript = `#!/bin/sh line=$1 file=$2 @@ -296,7 +343,7 @@ file=$2 pc_append "$line" "$file" ` -const merlinRemoveStartupScript = `#!/bin/sh +const merlinRemoveLineFromScript = `#!/bin/sh line=$1 file=$2 From 9689607409f5b53fe0e3683913ed508eac13d045 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 9 May 2023 23:11:53 +0700 Subject: [PATCH 49/55] all: wait NTP synced on Merlin On some Merlin routers, the time is broken when system reboot, and need to wait for NTP synced to get the correct time. For fetching API in cd mode successfully, ctrld need to wait until NTP set the time correctly, otherwise, the certificate validation would complain. --- cmd/ctrld/cli.go | 7 ++++++ internal/router/router.go | 50 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 2a12c12..daf672c 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -173,6 +173,13 @@ func initCLI() { // Log config do not have thing to validate, so it's safe to init log here, // so it's able to log information in processCDFlags. initLogging() + + if setupRouter { + if err := router.PreStart(); err != nil { + mainLog.Fatal().Err(err).Msg("failed to perform router pre-start check") + } + } + processCDFlags() if err := ctrld.ValidateConfig(validator.New(), &cfg); err != nil { mainLog.Fatal().Msgf("invalid config: %v", err) diff --git a/internal/router/router.go b/internal/router/router.go index bc628ad..1e330b4 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -2,14 +2,18 @@ package router import ( "bytes" + "context" "errors" + "fmt" "os" "os/exec" "sync" "sync/atomic" + "time" "github.com/fsnotify/fsnotify" "github.com/kardianos/service" + "tailscale.com/logtail/backoff" "github.com/Control-D-Inc/ctrld" ) @@ -89,6 +93,52 @@ func ConfigureService(sc *service.Config) error { return nil } +// PreStart blocks until the router is ready for running ctrld. +func PreStart() (err error) { + if Name() != DDWrt { + return nil + } + + pidFile := "/tmp/ctrld.pid" + // On Merlin, NTP may out of sync, so waiting for it to be ready. + // + // Remove pid file and trigger dnsmasq restart, so NTP can resolve + // server name and perform time synchronization. + pid, err := os.ReadFile(pidFile) + if err != nil { + return fmt.Errorf("PreStart: os.Readfile: %w", err) + } + if err := os.Remove(pidFile); err != nil { + return fmt.Errorf("PreStart: os.Remove: %w", err) + } + defer func() { + if werr := os.WriteFile(pidFile, pid, 0600); werr != nil { + err = errors.Join(err, werr) + return + } + if rerr := merlinRestartDNSMasq(); rerr != nil { + err = errors.Join(err, rerr) + return + } + }() + if err := merlinRestartDNSMasq(); err != nil { + return fmt.Errorf("PreStart: merlinRestartDNSMasq: %w", err) + } + + // Wait until `ntp_read=1` set. + b := backoff.NewBackoff("PreStart", func(format string, args ...any) {}, 10*time.Second) + for { + out, err := nvram("get", "ntp_ready") + if err != nil { + return fmt.Errorf("PreStart: nvram: %w", err) + } + if out == "1" { + return nil + } + b.BackOff(context.Background(), errors.New("ntp not ready")) + } +} + // PostInstall performs task after installing ctrld on router. func PostInstall() error { name := Name() From fa14f1dadf96ee20ff459e646f267dcdfeb5a458 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 15 May 2023 21:20:53 +0700 Subject: [PATCH 50/55] Fix wrong timeout in lookupIP The assignment is changed wrongly in process of refactoring parallel dialer for resolving bootstrap IP. While at it, also satisfy staticheck for jffs not enabled error. --- internal/router/ddwrt.go | 3 ++- internal/router/router.go | 2 +- resolver.go | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/router/ddwrt.go b/internal/router/ddwrt.go index 52a5732..3fe89a9 100644 --- a/internal/router/ddwrt.go +++ b/internal/router/ddwrt.go @@ -12,7 +12,8 @@ const ( nvramRCStartupKey = "rc_startup" ) -var ddwrtJffs2NotEnabledErr = errors.New(`could not install service without jffs, follow this guide to enable: +// lint:ignore ST1005 This error is for human. +var errDdwrtJffs2NotEnabled = errors.New(`could not install service without jffs, follow this guide to enable: https://wiki.dd-wrt.com/wiki/index.php/Journalling_Flash_File_System `) diff --git a/internal/router/router.go b/internal/router/router.go index 1e330b4..c8beac7 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -84,7 +84,7 @@ func ConfigureService(sc *service.Config) error { switch name { case DDWrt: if !ddwrtJff2Enabled() { - return ddwrtJffs2NotEnabledErr + return errDdwrtJffs2NotEnabled } case OpenWrt: sc.Option["SysvScript"] = openWrtScript diff --git a/resolver.go b/resolver.go index 1e07c78..befa298 100644 --- a/resolver.go +++ b/resolver.go @@ -143,7 +143,7 @@ func lookupIP(domain string, timeout int, withBootstrapDNS bool) (ips []string) ProxyLog.Debug().Msgf("Resolving %q using bootstrap DNS %q", domain, resolver.nameservers) timeoutMs := 2000 if timeout > 0 && timeout < timeoutMs { - timeoutMs = timeoutMs + timeoutMs = timeout } questionDomain := dns.Fqdn(domain) ipFromRecord := func(record dns.RR) string { From 57fa68970ae84775390c968f455cd739d0eb4397 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 15 May 2023 22:47:10 +0700 Subject: [PATCH 51/55] internal/router: fix lint ignore comment --- internal/router/ddwrt.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/router/ddwrt.go b/internal/router/ddwrt.go index 3fe89a9..b0be098 100644 --- a/internal/router/ddwrt.go +++ b/internal/router/ddwrt.go @@ -12,7 +12,7 @@ const ( nvramRCStartupKey = "rc_startup" ) -// lint:ignore ST1005 This error is for human. +//lint:ignore ST1005 This error is for human. var errDdwrtJffs2NotEnabled = errors.New(`could not install service without jffs, follow this guide to enable: https://wiki.dd-wrt.com/wiki/index.php/Journalling_Flash_File_System From d9dfc584e77861190bf7f20b273421cf1d266c42 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 15 May 2023 22:34:27 +0700 Subject: [PATCH 52/55] internal/router: disable DNSSEC on ddwrt/merlin --- internal/router/dnsmasq.go | 4 +++- internal/router/nvram.go | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/internal/router/dnsmasq.go b/internal/router/dnsmasq.go index 009bf27..b8fad8c 100644 --- a/internal/router/dnsmasq.go +++ b/internal/router/dnsmasq.go @@ -29,8 +29,10 @@ if [ -n "$pid" ] && [ -f "/proc/${pid}/cmdline" ]; then pc_append "no-resolv" "$config_file" # do not read /etc/resolv.conf pc_append "server=127.0.0.1#5354" "$config_file" # use ctrld as upstream {{- if .SendClientInfo}} - pc_append "add-mac" "$config_file" # add client mac + pc_append "add-mac" "$config_file" # add client mac {{- end}} + pc_delete "dnssec" "$config_file" # disable DNSSEC + pc_delete "trust-anchor=" "$config_file" # disable DNSSEC # For John fork pc_delete "resolv-file" "$config_file" # no WAN DNS settings diff --git a/internal/router/nvram.go b/internal/router/nvram.go index 79a7657..b66fcdb 100644 --- a/internal/router/nvram.go +++ b/internal/router/nvram.go @@ -18,6 +18,14 @@ func nvram(args ...string) (string, error) { return strings.TrimSpace(stdout.String()), nil } +/* +NOTE: + - For Openwrt, DNSSEC is not included in default dnsmasq (require dnsmasq-full). + - For Merlin, DNSSEC is configured during postconf script (see merlinDNSMasqPostConfTmpl). + - For Ubios UDM Pro/Dream Machine, DNSSEC is not included in their dnsmasq package: + +https://community.ui.com/questions/Implement-DNSSEC-into-UniFi/951c72b0-4d88-4c86-9174-45417bd2f9ca + +https://community.ui.com/questions/Enable-DNSSEC-for-Unifi-Dream-Machine-FW-updates/e68e367c-d09b-4459-9444-18908f7c1ea1 +*/ func nvramKV() map[string]string { switch Name() { case DDWrt: @@ -25,6 +33,7 @@ func nvramKV() map[string]string { "dns_dnsmasq": "1", // Make dnsmasq running but disable DNS ability, ctrld will replace it. "dnsmasq_options": "", // Configuration of dnsmasq set by ctrld, filled by setupDDWrt. "dns_crypt": "0", // Disable DNSCrypt. + "dnssec": "0", // Disable DNSSEC. } case Merlin: return map[string]string{ From 3b6c12abd41707dfa9221e3dd16b2ddf843d2834 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 15 May 2023 23:54:00 +0700 Subject: [PATCH 53/55] all: support GL.iNET router --- cmd/ctrld/cli.go | 5 +++++ cmd/ctrld/service.go | 32 ++++++++++++++++++++++++++++++++ internal/router/openwrt.go | 10 ++++++++++ 3 files changed, 47 insertions(+) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index daf672c..a0304da 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -137,6 +137,7 @@ func initCLI() { if err != nil { mainLog.Fatal().Err(err).Msg("failed create new service") } + s = newService(s) serviceLogger, err := s.Logger(nil) if err != nil { mainLog.Error().Err(err).Msg("failed to get service logger") @@ -310,6 +311,7 @@ func initCLI() { mainLog.Error().Msg(err.Error()) return } + s = newService(s) tasks := []task{ {s.Stop, false}, {s.Uninstall, false}, @@ -371,6 +373,7 @@ func initCLI() { mainLog.Error().Msg(err.Error()) return } + s = newService(s) initLogging() if doTasks([]task{{s.Stop, true}}) { prog.resetDNS() @@ -394,6 +397,7 @@ func initCLI() { mainLog.Error().Msg(err.Error()) return } + s = newService(s) initLogging() if doTasks([]task{{s.Restart, true}}) { mainLog.Notice().Msg("Service restarted") @@ -414,6 +418,7 @@ func initCLI() { mainLog.Error().Msg(err.Error()) return } + s = newService(s) status, err := serviceStatus(s) if err != nil { mainLog.Error().Msg(err.Error()) diff --git a/cmd/ctrld/service.go b/cmd/ctrld/service.go index 43d3261..cc42f8b 100644 --- a/cmd/ctrld/service.go +++ b/cmd/ctrld/service.go @@ -7,8 +7,40 @@ import ( "os/exec" "github.com/kardianos/service" + + "github.com/Control-D-Inc/ctrld/internal/router" ) +func newService(s service.Service) service.Service { + // TODO: unify for other SysV system. + if router.IsGLiNet() { + return &sysV{s} + } + return s +} + +// sysV wraps a service.Service, and provide start/stop/status command +// base on "/etc/init.d/". +// +// Use this on system wherer "service" command is not available, like GL.iNET router. +type sysV struct { + service.Service +} + +func (s *sysV) Start() error { + _, err := exec.Command("/etc/init.d/ctrld", "start").CombinedOutput() + return err +} + +func (s *sysV) Stop() error { + _, err := exec.Command("/etc/init.d/ctrld", "stop").CombinedOutput() + return err +} + +func (s *sysV) Status() (service.Status, error) { + return unixSystemVServiceStatus() +} + type task struct { f func() error abortOnError bool diff --git a/internal/router/openwrt.go b/internal/router/openwrt.go index 97f4628..6e5881d 100644 --- a/internal/router/openwrt.go +++ b/internal/router/openwrt.go @@ -13,6 +13,16 @@ var errUCIEntryNotFound = errors.New("uci: Entry not found") const openwrtDNSMasqConfigPath = "/tmp/dnsmasq.d/ctrld.conf" +// IsGLiNet reports whether the router is an GL.iNet router. +func IsGLiNet() bool { + if Name() != OpenWrt { + return false + } + buf, _ := os.ReadFile("/proc/version") + // The output of /proc/version contains "(glinet@glinet)". + return bytes.Contains(buf, []byte(" (glinet")) +} + func setupOpenWrt() error { // Delete dnsmasq port if set. if _, err := uci("delete", "dhcp.@dnsmasq[0].port"); err != nil && !errors.Is(err, errUCIEntryNotFound) { From 6cd451acec5e62b98160c4b490a7d79c843c4e2b Mon Sep 17 00:00:00 2001 From: Yegor Sak Date: Mon, 15 May 2023 17:12:27 +0000 Subject: [PATCH 54/55] Update README.md --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 25bef34..76d9e46 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,17 @@ Once you run the above command, the following things will happen: - Your default network interface will be updated to use the listener started by the service - All OS DNS queries will be sent to the listener +### Router Mode +You can run `ctrld` on any supported router, which will function similarly to the Service Mode mentioned above. The list of supported routers and firmware includes: +- OpenWRT +- DD-WRT +- Asus Merlin +- GL.iNet +- Ubiquiti + +In order to start `ctrld` as a DNS provider, simply run `./ctrld setup auto` command. You can optionally supply the `--cd` flag on order to configure a specific Control D device on the router. + + ## Configuration See [Configuration Docs](docs/config.md). From 3a2024ebd71e40796349fd42f0f81695f224877d Mon Sep 17 00:00:00 2001 From: Yegor Sak Date: Mon, 15 May 2023 17:16:07 +0000 Subject: [PATCH 55/55] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 76d9e46..8002154 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ All DNS protocols are supported, including: - Windows (386, amd64, arm) - Mac (amd64, arm64) - Linux (386, amd64, arm, mips) +- Common routers (See Router Mode below) ## Download Download pre-compiled binaries from the [Releases](https://github.com/Control-D-Inc/ctrld/releases) section. @@ -148,6 +149,8 @@ You can run `ctrld` on any supported router, which will function similarly to th In order to start `ctrld` as a DNS provider, simply run `./ctrld setup auto` command. You can optionally supply the `--cd` flag on order to configure a specific Control D device on the router. +In this mode, and when Control D upstreams are used, the router will [relay your network topology](https://docs.controld.com/docs/device-clients) to Control D (LAN IPs, MAC addresses, and hostnames), and you will be able to see your LAN devices in the web panel, view analytics and apply unique profiles to them. + ## Configuration See [Configuration Docs](docs/config.md).