From 62f94ac6a1404834ac6f0723ef57e25fcd5e67f9 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Sat, 1 Jun 2024 15:35:45 +0200 Subject: Improve terminology and documentation --- README.md | 119 ++++++++++++++++++++++++++----------------------- docs/design.md | 100 +++++++++++++++++++++++++++++++++++++++++ docs/feedback.md | 23 ++++++++++ docs/introduction.md | 103 ------------------------------------------ docs/storage.md | 3 -- docs/submission.md | 22 --------- pkg/policy/node.go | 8 ++-- pkg/policy/policy.go | 2 +- pkg/policy/wildcard.go | 6 +-- 9 files changed, 193 insertions(+), 193 deletions(-) create mode 100644 docs/design.md create mode 100644 docs/feedback.md delete mode 100644 docs/introduction.md delete mode 100644 docs/storage.md delete mode 100644 docs/submission.md diff --git a/README.md b/README.md index ef27a10..bda9f48 100644 --- a/README.md +++ b/README.md @@ -2,83 +2,83 @@ An implementation of a silent Certificate Transparency monitor. -**Status:** drafty prototype, please do not use for anything serious. +## Status + +Drafty prototype, please do not use for anything too serious yet. ## How it works A monitor downloads all certificates from Certificate Transparency logs. This -gives a concise view of which certificates have been issued for what domains. +provides a concise view of which certificates have been issued for what domains. -The monitor is configured to pull legitimately issued certificates from trusted -nodes. Each node packages its legitimate certificates as a single submission. -This submission is then made available for download on a URL via HTTP GET. +The monitor is configured to pull certificates from trusted systems that +legitimately request certificates. The legitimately issued certificates are +typically made available on HTTP(S) URLs that are polled periodically. -To convince the monitor that a submission was generated by a trusted node, a -shared secret is established between the node and the monitor. The node creates -a message authentication code using the shared secret. The monitor verifies it. +To convince the monitor that the file of legitimately issued certificates is +authentic, it is integrity protected using a message authentication code. So, +the monitor and its trusted systems need to be configured with shared secrets. -What makes this setup _silent_ is the fact that the monitor can compute the -difference between any discovered and legitimately issued certificates. If a -certificate is found that no node submitted, only then is an alert printed. +What makes this setup "silent" is that the monitor can compute the difference +between any downloaded and legitimately issued certificates. If a certificate +is found that no trusted system made available, only then an alert is emitted. -## Quick start +See the [silentct design](./docs/design.md) for a lengthier introduction. -### Setup a node +## Quickstart -You will need the `silentct-mac` tool to create submissions that the monitor can -pull. Install: +### Setup a trusted system - $ go install rgdd.se/silentct/cmd/silentct-mac@latest +Install the `silentct-mac` tool. -Locate the node's certificates that are still valid (i.e., not expired) and -prepare a submission for them: + $ go install rgdd.se/silentct/cmd/silentct-mac@latest - $ silentct-mac -n NAME -s SECRET /path/to/chain-1.pem /path/to/chain-2.pem ... +Mark all certificates that have yet to expire as legitimately issued. The below +specifies one certificate chain, but it is possible to list multiple ones. -`NAME` is an arbitrary name of the node. + $ silentct-mac -n example.org -s sikritpassword /etc/letsencrypt/live/example.org/fullchain.pem -`SECRET` is a secret that will only be shared with the monitor. +`-n` sets an arbitrary name of the trusted system. -The output includes the node name, the computed message authentication code, and -the list of certificate chains that was specified. Use the `-o` option if you -prefer to save the output directly to a file rather than getting it on stdout. +`-s` sets a secret that will be shared with the monitor. -Make the generated submission file available on a URL. Typically each node -already serves web content, in which case you can copy it into some directory. +The output includes the name, a message authentication code, and the list of +certificate chains that was specified. Record the output as a file the monitor +can pull, e.g., by saving it in a web root or transferring it to one. Ensure +that this file gets updated each time a new certificate is legitimately issued. +Below is an example that keeps the file up-to-date with `crontab` and `certbot`. -Finally, ensure that anytime the node requests a new certificate to be issued, -then a new submission is generated that replaces or extends the previous one. -For example, if `certbot` is run by `cron`, then that is a good place to hook. + # crontab -l + 35 3 * * * certbot renew --post-hook "silentct-mac -n example.org -s sikritpassword -o /var/www/example.org/silentct/allowlist /etc/letsencrypt/live/example.org/fullchain.pem" -Repeat this setup if there are multiple nodes. +**Note:** the `--post-hook` option can be specified per site in the +`[renewalparams]` section of `/etc/letsencrypt/renewal/example.org.conf`. ### Setup the monitor -Install on the system that will run the monitor: +Install the `silentct-mon` tool: $ go install rgdd.se/silentct/cmd/silentct-mon@latest -Create a monitor policy file in JSON format. Below is an example that looks for -all certificates related to `example.org`, expect for certificates that are -associated with `test.example.org`. You need at least one wildcard to match on. +Create a configuration file. - $ cat policy.json + $ cat config.json { "monitor": [ { "bootstrap_at": "2024-01-01T00:00:00Z", - "wildcard": "example.org", + "suffix": "example.org", "excludes": [ "test" ] } ], - "nodes": [ + "certificate_requesters": [ { - "name": "NODE_NAME", - "secret": "NODE_SECRET", - "url": "SUBMISSION_URL", - "issues": [ + "name": "example.org", + "secret": "sikritpassword", + "location": "https://www.example.org/silentct/allowlist", + "requests": [ "example.org", "www.example.org" ] @@ -86,30 +86,35 @@ associated with `test.example.org`. You need at least one wildcard to match on. ] } -The `excludes` keyword is optional. The `bootstrap_at` keyword is required, and -should be set to the current time of adding the wildcard for monitoring. Any -certificate that expired before the specified bootstrap time will be ignored. +`bootstrap_at` is the time the monitor started looking for certificates that +match `suffix`. The monitor considers a certificate to match iff (i) it expired +after the bootstrap time, and (ii) at least one subject alternative name ends +with the specified suffix without a longer match being available when taking the +optional `excludes` list of subdomains into account. For example, the above +configuration matches `www.example.org` but not `foo.test.example.org`. -Populate the list of nodes based on the names, secrets, and URLs selected during -your setup. Also add the domains each node is allowed to put into certificates. +Each entry in the `"certificate-requesters"` list corresponds to a trusted +system and the domains it requests certificates for. Set `name`, `secret`, +`location` (filename or URL to pull from), and `requests` to match the +configuration of each trusted system. The monitor will refuse to mark a +certificate as legitimate unless the trusted system that requested it had +permission to do so. This adds a layer of separation between trusted systems. -Bootstrap the monitor in a non-existent directory: +### Start the monitor - $ silentct-mon --bootstrap -f policy.json -d /path/to/directory -v INFO - ... +Start the monitor: -Leave the monitor running: + $ silentct-mon -c config.json -d ~/.local/lib/silentct - $ silentct-mon -f policy.json -d /path/to/directory +Use the `--bootstrap` flag when running the monitor for the first time. -Any noteworthy events (like a potentially mis-issued certificate that no node -submitted) will be printed on stdout. If you prefer to get the monitor's output -in a file, use the `-o` option. Please note that it is your job to watch the -output, and/or to hook it up to a notification system like email or similar. +Noteworthy events will be printed on stdout using the NOTICE level. If you +prefer to get the monitor's output in a file, use the `-o` option. -### Further documentation +### Stop the monitor - - A lengthier [introduction](./docs/introduction.md) to the overall design +Stop the monitor gracefully by sending the SIGINT or SIGTERM signals. Nothing +bad will happen on an ungraceful exit (just redundant work on next startup). ## Contact diff --git a/docs/design.md b/docs/design.md new file mode 100644 index 0000000..83f2b59 --- /dev/null +++ b/docs/design.md @@ -0,0 +1,100 @@ +# silentct + +This document introduces a silent Certificate Transparency monitor design. + +## Setting + +We consider a setting where one or more trusted systems request certificates for +a list of domains. The domains that a system request certificates for may +overlap with the domains of other systems. For example, there may be two +distinct systems that host and request certificates for `www.example.org`. +Other examples of "systems" that request certificates could include +`jitsi.example.org`, `etherpad.example.org` and `gitlab.example.org`. + +The threat we are worried about is certificate mis-issuance. Due to considering +a multi-system setting with overlapping domains, no single system can be aware +of all legitimately issued certificates for the domains that are being managed. + +A certificate is considered mis-issued if it contains: + + 1. at least one domain that any of the trusted systems manage _but without any + of the trusted systems requesting that certificate to be issued_, or + 2. at least one subdomain of the domains that any of the trusted systems + manage _unless that subdomain is explicitly specified as out of scope_. + +The cause of certificate mis-issuance can vary, ranging from BGP and DNS hijacks +to certificate authorities that are coerced, compromised, or actively malicious. + +## Goals and non-scope + +The goal is to detect certificate mis-issuance. It is however out of scope to +detect certificate mis-issuance that happened in the past. In other words, if +the design described herein is put into operation at time `T`, then any +certificate mis-issuance that happened before time `T` is out of scope. This is +an important constraint that makes it _a lot less costly_ to bootstrap the +monitor. For example, old certificate backlogs can simply be ignored. + +It is also out of scope to detect certificate mis-issuance that targets web +browsers without Certificate Transparency enforcement. This is because we +cannot get a concise view of all certificates without Certificate Transparency. + +To detect certificate mis-issuance, we want to construct a monitor that: + + 1. _is easy to self-host_, because you trust yourself or can then (more + easily) find someone you trust to do the monitoring on your behalf, and + 2. _is silent_, so that there is little or no noise unless certificate + mis-issuance is actually suspected. In other words, there should not be a + notification every time a legitimate certificate is issued or renewed. + +The "silent" property helps a lot for system administrators that manage more +than a few certificates. It also helps in the third-party monitoring setting, +as it would not be more noisy to subscribe to notifications from >1 monitor. + +## Assumptions + + - The attacker is unable to control two independent logs that count towards + the SCT checks in web browsers. So, we need not worry about split-views and + can just download the logs while verifying that they are locally consistent. + - The systems that request certificates start in good states but may be + compromised sometime in the future. Detection of certificate mis-issuance + is then out of scope for all domains that the compromised systems managed. + - A mis-issued certificate will only be used to target connections from a + fixed set of IP addresses. A party that can distinguish between + certificates that are legitimate and mis-issued will never be targeted. + - A domain owner notices alerts about suspected certificate mis-issuance. The + monitor that generates these alerts is trusted and never compromised. + +## Architecture + +A monitor downloads all certificates that are issued by certificate authorities +from Certificate Transparency logs. The exact logs to download is automatically +updated using a list that Google publishes in signed form. All historical +updates to the list of logs is stored locally in case any issues are suspected. + +(It is possible to get INFO output whenever logs are added and removed. The +default verbosity is however NOTICE, which aims to be as silent as possible.) + +To filter out certificates that are not relevant, the monitor is configured with +a list of domains to match on. Only matching certificates will be stored, which +means there are nearly no storage requirements to run this type of monitor. + +To get the "silent" property, the monitor pulls the trusted systems for +legitimately issued certificates via HTTP GET. Alternatively, the monitor can +read a local file in case it is co-located with a single trusted system. The +monitor uses this as [feedback](./feedback.md) to filter the downloaded +certificates that matched. If a certificate is found that none of the trusted +systems made available, only then is an alert emitted (NOTICE level output). + +The communication channel between the trusted systems and the monitor can be +tampered with. For example, it may be plain HTTP or an HTTPS connection that +the attacker trivially hijacks by obtaining yet another mis-issued certificate. +Owning that the communication channel is insecure helps avoid misconfiguration. + +A shared secret is used for each system to authenticate with the monitor. This +secret is never shown on the wire: an HMAC key is derived from it, which is used +to produce message authentication codes. All a machine-in-the-middle attacker +can do is replay or block integrity-protected files that a system generated. + +"Replays" can happen either way because the monitor polls periodically, i.e., +the monitor needs to account for the fact that it may poll the same file twice. +Blocking can not be solved by cryptography and would simply result in alerts. diff --git a/docs/feedback.md b/docs/feedback.md new file mode 100644 index 0000000..d79d57f --- /dev/null +++ b/docs/feedback.md @@ -0,0 +1,23 @@ +# Feedback + +This document describes the integrity-protected file format that a trusted +system uses when making legitimately issued certificates available to a monitor. + +## Format + + NAME MAC + + ... + + +`NAME`: identifier that the monitor uses to locate the shared secret. + +`MAC`: HMAC with SHA256 as the hash function, computed for line two and forward. +The shared HMAC key is derived as follows by the trusted system and the monitor: + + hkdf := hkdf.New(sha256.New, SECRET, []byte("silentct"), NAME) + key := make([]byte, 16) + io.ReadFull(hkdf, key) + +``: certificate chain in PEM format that the trusted system +considers legitimate. Can be repeated, then delimited by "silentct:separator". diff --git a/docs/introduction.md b/docs/introduction.md deleted file mode 100644 index 0aab2cc..0000000 --- a/docs/introduction.md +++ /dev/null @@ -1,103 +0,0 @@ -# Silent Certificate Transparency - -This document introduces a silent Certificate Transparency monitor design. - -## Setting - -We consider a setting where one or more trusted _nodes_ request certificates for -a specified list of domain names. The domain names that a node requests -certificates for may overlap with the domain names of other nodes. For example, -there may be two distinct nodes that request certificates for a given domain. - -The threat we are worried about is certificate mis-issuance. Due to considering -a multi-node setting with overlapping domain names, no single node can be aware -of all legitimately issued certificates for the domain names that it manages. - -A certificate is considered mis-issued if it contains: - - 1. at least one domain name that any of the trusted nodes manage _but without - any of the trusted nodes requesting that certificate to be issued_, or - 2. at least one subdomain of the domain names that any of the trusted nodes - manage _unless that subdomain is explicitly specified as out of scope_. - -The cause of certificate mis-issuance can vary, ranging from BGP and DNS hijacks -to certificate authorities that are coerced, compromised, or actively malicious. - -## Goals and non-scope - -The goal is to detect certificate mis-issuance, not to prevent it. It is out of -scope to detect certificate mis-issuance that happened in the past. In other -words, if the architecture described herein is put into operation at time `T`, -then any certificate mis-issuance that happened before time `T` is out of scope. - -It is also out of scope to detect certificate mis-issuance that targets web -browsers without Certificate Transparency enforcement. This is because we -cannot get a concise view of all certificates without Certificate Transparency. - -To achieve the goal of certificate mis-issuance, we want a _monitor_ that: - - 1. _is easy to self-host_, because you trust yourself or can then find someone - else that is appropriate and willing to host your infrastructure, and - 2. _is silent_, so that there is little or no noise unless certificate - mis-issuance is suspected or other noteworthy log events are happening. - -## Assumptions - - - The attacker is unable to control two independent logs that count towards - the SCT checks in web browsers. So, we need not worry about split-views and - can just download the logs while verifying that they are locally consistent. - - The nodes that request certificates start in good states but may be - compromised sometime in the future. Detection of certificate mis-issuance - is then out of scope for all domains that the compromised nodes managed. - - A mis-issued certificate will only be used to target connections from a - fixed set of IP addresses. Any party that can distinguish between - certificates that are legitimate and mis-issued will never be targeted. - - A domain owner notices alerts about suspected certificate mis-issuance. The - monitor that generates these alerts is trusted and never compromised. - -## Architecture - -A monitor downloads all certificates that are issued by certificate authorities -from Certificate Transparency logs. The exact logs to download is automatically -updated using a list that Google publishes in signed form. All historical -updates to the list of logs is stored locally in case any issues are suspected. - -(It is possible to get INFO output whenever logs are added and removed. The -default verbosity is however NOTICE, which aims to be as silent as possible.) - -To filter out certificates that are not relevant, the monitor is configured with -a list of domains to match on. Only matching certificates will be stored, which -means there are nearly no storage requirements to run this type of monitor. - -To get the property of _silence_, the monitor pulls the trusted nodes via HTTP -GET for legitimately issued certificates (periodic job). The monitor will use -this feedback to filter the downloaded certificates that matched. If any -certificates are found that no node pushed to the monitor, an alert is printed. - -The communication channel between the trusted nodes and the monitor can be -tampered with. For example, it may be plain HTTP or an HTTPS connection that -the attacker trivially hijacks by obtaining yet another mis-issued certificate. -Owning that the communication channel is insecure helps avoid misconfiguration. - -A shared secret is used for each node to authenticate with the monitor. This -secret is never shown on the wire: an HMAC key is derived from it, which is used -to produce message authentication codes. All a machine-in-the-middle attacker -can do is replay or block integrity-protected submissions that a node generated. - -"Replays" can happen either way because the monitor polls periodically, i.e., -the monitor needs to account for the fact that it may poll the same thing twice. -Blocking can not be solved by cryptography and would simply result in alerts. - -## Further reading - -docdoc - -## Future ideas - - - Reduce the amount of bandwidth that the monitor spends downloading - certificates that are either way discarded (non-matches). This can be - achieved by introducing a _verifiable proxy_ supporting wildcard - (non-)membership proofs, see [verifiable light-weight monitoring][]. Ignore - the parts about changing the logs; that is easily solved by the proxy alone. - -[verifiable light-weight monitoring]: https://arxiv.org/pdf/1711.03952.pdf diff --git a/docs/storage.md b/docs/storage.md deleted file mode 100644 index a0616ed..0000000 --- a/docs/storage.md +++ /dev/null @@ -1,3 +0,0 @@ -# Storage - -docdoc diff --git a/docs/submission.md b/docs/submission.md deleted file mode 100644 index 1d9c189..0000000 --- a/docs/submission.md +++ /dev/null @@ -1,22 +0,0 @@ -# Submission - -docdoc - -## Format - - NAME MAC - - silentct:separator - ... - - -`NAME`: identifier that the monitor uses to locate the right secret. - -`MAC`: HMAC with SHA256 as the hash function, computed for line two and forward. -The HMAC key is derived by the node and the monitor from their shared secret: - - hkdf := hkdf.New(sha256.New, SECRET, []byte("silentct"), NAME) - key := make([]byte, 16) - io.ReadFull(hkdf, key) - -``: certificate chain in PEM format the node considers legitimate. diff --git a/pkg/policy/node.go b/pkg/policy/node.go index 607dbc3..d933e7f 100644 --- a/pkg/policy/node.go +++ b/pkg/policy/node.go @@ -11,10 +11,10 @@ import ( ) type Node struct { - Name string `json:"name"` // Artbirary node name to authenticate - Secret string `json:"secret"` // Arbitrary node secret for authentication - URL string `json:"url"` // Where the node's submissions can be downloaded - Domains []string `json:"issues"` // Exact-match domain names allowed to be issued + Name string `json:"name"` // Artbirary node name to authenticate + Secret string `json:"secret"` // Arbitrary node secret for authentication + URL string `json:"location"` // Where the node's submissions can be downloaded + Domains []string `json:"requests"` // Exact-match domain names allowed to be issued key [16]byte } diff --git a/pkg/policy/policy.go b/pkg/policy/policy.go index 8ee4867..3c2fec2 100644 --- a/pkg/policy/policy.go +++ b/pkg/policy/policy.go @@ -10,7 +10,7 @@ import ( type Policy struct { Monitor Wildcards `json:"monitor"` - Nodes []Node `json:"nodes"` + Nodes []Node `json:"certificate_requesters"` // Optional StaticLogs []metadata.Log `json:"static_logs"` diff --git a/pkg/policy/wildcard.go b/pkg/policy/wildcard.go index c67e1d9..b0bdf61 100644 --- a/pkg/policy/wildcard.go +++ b/pkg/policy/wildcard.go @@ -38,7 +38,7 @@ func (w *Wildcards) match(sans []string, notAfter time.Time) bool { // 2. the certificate expired before the BootstrapAt timestamp. type Wildcard struct { BootstrapAt time.Time `json:"bootstrap_at"` - Wildcard string `json:"wildcard"` + Wildcard string `json:"suffix"` Excludes []string `json:"excludes,omitempty"` } @@ -52,10 +52,10 @@ func (w *Wildcard) UnmarshalJSON(data []byte) error { func (w *Wildcard) Validate() error { if w.BootstrapAt.IsZero() { - return fmt.Errorf("bootstrap time is required") + return fmt.Errorf("bootstrap_at is required") } if len(w.Wildcard) == 0 { - return fmt.Errorf("wildcard is required") + return fmt.Errorf("suffix is required") } return nil } -- cgit v1.2.3