diff --git a/cmd/camget/camget.go b/cmd/camget/camget.go index 7a6115943..965fde2ea 100644 --- a/cmd/camget/camget.go +++ b/cmd/camget/camget.go @@ -45,6 +45,7 @@ var ( flagGraph = flag.Bool("graph", false, "Output a graphviz directed graph .dot file of the provided root schema blob, to be rendered with 'dot -Tsvg -o graph.svg graph.dot'") flagContents = flag.Bool("contents", false, "If true and the target blobref is a 'bytes' or 'file' schema blob, the contents of that file are output instead.") flagShared = flag.String("shared", "", "If non-empty, the URL of a \"share\" blob. The URL will be used as the root of future fetches. Only \"haveref\" shares are currently supported.") + flagTrustedCert = flag.String("cert", "", "If non-empty, the fingerprint (20 digits lowercase prefix of the SHA256 of the complete certificate) of the TLS certificate we trust for the share URL. Requires --shared.") flagInsecureTLS = flag.Bool("insecure", false, "If set, when using TLS, the server's certificates verification is disabled, and they are not checked against the trustedCerts in the client configuration either.") ) @@ -72,13 +73,17 @@ func main() { log.Fatal("No arguments permitted when using --shared") } cl1, target, err := client.NewFromShareRoot(*flagShared, - client.OptionInsecure(*flagInsecureTLS)) + client.OptionInsecure(*flagInsecureTLS), + client.OptionTrustedCert(*flagTrustedCert)) if err != nil { log.Fatal(err) } cl = cl1 items = append(items, target) } else { + if *flagTrustedCert != "" { + log.Fatal("Can't use --cert without --shared.") + } cl = client.NewOrFail() for n := 0; n < flag.NArg(); n++ { arg := flag.Arg(n) diff --git a/pkg/client/client.go b/pkg/client/client.go index 355008f72..14e01ede7 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -78,10 +78,8 @@ type Client struct { haveCache HaveCache initTrustedCertsOnce sync.Once - // We define a certificate fingerprint as the 10 digits lowercase prefix - // of the SHA1 of the complete certificate (in ASN.1 DER encoding). - // It is the same as what 'openssl x509 -fingerprint' shows and what - // web browsers commonly use (except truncated to 10 digits). + // We define a certificate fingerprint as the 20 digits lowercase prefix + // of the SHA256 of the complete certificate (in ASN.1 DER encoding). // trustedCerts contains the fingerprints of the self-signed // certificates we trust. // If not empty, (and if using TLS) the full x509 verification is @@ -210,6 +208,23 @@ func (o optionInsecure) modifyClient(c *Client) { c.InsecureTLS = bool(o) } +func OptionTrustedCert(cert string) ClientOption { + return optionTrustedCert(cert) +} + +type optionTrustedCert string + +func (o optionTrustedCert) modifyClient(c *Client) { + cert := string(o) + if cert != "" { + c.initTrustedCertsOnce.Do(noop) + c.trustedCerts = []string{string(o)} + } +} + +// noop is for use with sync.Onces. +func noop() {} + var shareURLRx = regexp.MustCompile(`^(.+)/(` + blob.Pattern + ")$") // NewFromShareRoot uses shareBlobURL to set up and return a client that @@ -221,10 +236,8 @@ func NewFromShareRoot(shareBlobURL string, opts ...ClientOption) (c *Client, tar return nil, blob.Ref{}, fmt.Errorf("Unkown share URL base") } c = New(m[1]) - c.discoOnce.Do(func() { /* nothing */ - }) - c.prefixOnce.Do(func() { /* nothing */ - }) + c.discoOnce.Do(noop) + c.prefixOnce.Do(noop) c.prefixv = m[1] c.isSharePrefix = true c.authMode = auth.None{}