1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package cmd
18
19 import (
20 "context"
21 "crypto/tls"
22 "flag"
23 "fmt"
24 "net/http"
25 "os"
26 "strings"
27 "time"
28
29 ct "github.com/google/certificate-transparency-go"
30 "github.com/google/certificate-transparency-go/client"
31 "github.com/google/certificate-transparency-go/jsonclient"
32 "github.com/google/certificate-transparency-go/loglist3"
33 "github.com/google/certificate-transparency-go/x509util"
34 "github.com/spf13/cobra"
35 "github.com/spf13/pflag"
36 "k8s.io/klog/v2"
37 )
38
39 const connectionFlags = "{--log_uri uri | --log_name name [--log_list {file|uri}]} [--pub_key file]"
40
41 var (
42 skipHTTPSVerify bool
43 logName string
44 logList string
45 logURI string
46 pubKey string
47 )
48
49 func init() {
50
51 pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
52
53 flags := rootCmd.PersistentFlags()
54 flags.BoolVar(&skipHTTPSVerify, "skip_https_verify", false, "Skip verification of HTTPS transport connection")
55 flags.StringVar(&logName, "log_name", "", "Name of log to retrieve information from --log_list for")
56 flags.StringVar(&logList, "log_list", loglist3.AllLogListURL, "Location of master log list (URL or filename)")
57 flags.StringVar(&logURI, "log_uri", "https://ct.googleapis.com/rocketeer", "CT log base URI")
58 flags.StringVar(&pubKey, "pub_key", "", "Name of file containing log's public key")
59 }
60
61
62 var rootCmd = &cobra.Command{
63 Use: "ctclient",
64 Short: "A command line client for Certificate Transparency logs",
65
66 PersistentPreRun: func(cmd *cobra.Command, _ []string) {
67 flag.Parse()
68 },
69 }
70
71
72
73 func Execute() {
74 if err := rootCmd.Execute(); err != nil {
75 klog.Fatal(err)
76 }
77 }
78
79 func signatureToString(signed *ct.DigitallySigned) string {
80 return fmt.Sprintf("Signature: Hash=%v Sign=%v Value=%x", signed.Algorithm.Hash, signed.Algorithm.Signature, signed.Signature)
81 }
82
83 func exitWithDetails(err error) {
84 if err, ok := err.(client.RspError); ok {
85 klog.Infof("HTTP details: status=%d, body:\n%s", err.StatusCode, err.Body)
86 }
87 klog.Exit(err.Error())
88 }
89
90 func connect(ctx context.Context) *client.LogClient {
91 var tlsCfg *tls.Config
92 if skipHTTPSVerify {
93 klog.Warning("Skipping HTTPS connection verification")
94 tlsCfg = &tls.Config{InsecureSkipVerify: skipHTTPSVerify}
95 }
96 httpClient := &http.Client{
97 Timeout: 10 * time.Second,
98 Transport: &http.Transport{
99 TLSHandshakeTimeout: 30 * time.Second,
100 ResponseHeaderTimeout: 30 * time.Second,
101 MaxIdleConnsPerHost: 10,
102 DisableKeepAlives: false,
103 MaxIdleConns: 100,
104 IdleConnTimeout: 90 * time.Second,
105 ExpectContinueTimeout: 1 * time.Second,
106 TLSClientConfig: tlsCfg,
107 },
108 }
109 opts := jsonclient.Options{UserAgent: "ct-go-ctclient/1.0"}
110 if pubKey != "" {
111 pubkey, err := os.ReadFile(pubKey)
112 if err != nil {
113 klog.Exit(err)
114 }
115 opts.PublicKey = string(pubkey)
116 }
117
118 uri := logURI
119 if logName != "" {
120 llData, err := x509util.ReadFileOrURL(logList, httpClient)
121 if err != nil {
122 klog.Exitf("Failed to read log list: %v", err)
123 }
124 ll, err := loglist3.NewFromJSON(llData)
125 if err != nil {
126 klog.Exitf("Failed to build log list: %v", err)
127 }
128
129 logs := ll.FindLogByName(logName)
130 if len(logs) == 0 {
131 klog.Exitf("No log with name like %q found in loglist %q", logName, logList)
132 }
133 if len(logs) > 1 {
134 logNames := make([]string, len(logs))
135 for i, log := range logs {
136 logNames[i] = fmt.Sprintf("%q", log.Description)
137 }
138 klog.Exitf("Multiple logs with name like %q found in loglist: %s", logName, strings.Join(logNames, ","))
139 }
140 uri = logs[0].URL
141 if opts.PublicKey == "" {
142 opts.PublicKeyDER = logs[0].Key
143 }
144 }
145
146 klog.V(1).Infof("Use CT log at %s", uri)
147 logClient, err := client.New(uri, httpClient, opts)
148 if err != nil {
149 klog.Exit(err)
150 }
151
152 return logClient
153 }
154
View as plain text