From d55c3b31ebb77df65cc052dbddc137cbe07b297e Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Thu, 11 Jun 2020 16:19:07 -0600 Subject: caddyhttp: Add client cert SAN placeholders --- modules/caddyhttp/app.go | 6 +++ modules/caddyhttp/caddyauth/caddyfile.go | 2 +- modules/caddyhttp/replacer.go | 64 ++++++++++++++++++++++++++++++-- modules/caddyhttp/replacer_test.go | 20 ++++++++++ 4 files changed, 87 insertions(+), 5 deletions(-) (limited to 'modules') diff --git a/modules/caddyhttp/app.go b/modules/caddyhttp/app.go index f695276..45845ea 100644 --- a/modules/caddyhttp/app.go +++ b/modules/caddyhttp/app.go @@ -72,9 +72,15 @@ func init() { // `{http.request.tls.proto_mutual}` | The negotiated next protocol was advertised by the server // `{http.request.tls.server_name}` | The server name requested by the client, if any // `{http.request.tls.client.fingerprint}` | The SHA256 checksum of the client certificate +// `{http.request.tls.client.public_key}` | The public key of the client certificate. +// `{http.request.tls.client.public_key_sha256}` | The SHA256 checksum of the client's public key. // `{http.request.tls.client.issuer}` | The issuer DN of the client certificate // `{http.request.tls.client.serial}` | The serial number of the client certificate // `{http.request.tls.client.subject}` | The subject DN of the client certificate +// `{http.request.tls.client.san.dns_names.*}` | SAN DNS names(index optional) +// `{http.request.tls.client.san.emails.*}` | SAN email addresses (index optional) +// `{http.request.tls.client.san.ips.*}` | SAN IP addresses (index optional) +// `{http.request.tls.client.san.uris.*}` | SAN URIs (index optional) // `{http.request.uri.path.*}` | Parts of the path, split by `/` (0-based from left) // `{http.request.uri.path.dir}` | The directory, excluding leaf filename // `{http.request.uri.path.file}` | The filename of the path, excluding directory diff --git a/modules/caddyhttp/caddyauth/caddyfile.go b/modules/caddyhttp/caddyauth/caddyfile.go index 13e78fc..afa3808 100644 --- a/modules/caddyhttp/caddyauth/caddyfile.go +++ b/modules/caddyhttp/caddyauth/caddyfile.go @@ -27,7 +27,7 @@ func init() { // parseCaddyfile sets up the handler from Caddyfile tokens. Syntax: // -// basicauth [] [] { +// basicauth [] [ []] { // [] // ... // } diff --git a/modules/caddyhttp/replacer.go b/modules/caddyhttp/replacer.go index 37b53d2..b3c7b5d 100644 --- a/modules/caddyhttp/replacer.go +++ b/modules/caddyhttp/replacer.go @@ -28,6 +28,7 @@ import ( "net" "net/http" "net/textproto" + "net/url" "path" "strconv" "strings" @@ -163,7 +164,7 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo if strings.HasPrefix(key, reqHostLabelsReplPrefix) { idxStr := key[len(reqHostLabelsReplPrefix):] idx, err := strconv.Atoi(idxStr) - if err != nil { + if err != nil || idx < 0 { return "", false } reqHost, _, err := net.SplitHostPort(req.Host) @@ -171,9 +172,6 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo reqHost = req.Host // OK; assume there was no port } hostLabels := strings.Split(reqHost, ".") - if idx < 0 { - return "", false - } if idx > len(hostLabels) { return "", true } @@ -245,6 +243,64 @@ func getReqTLSReplacement(req *http.Request, key string) (interface{}, bool) { return nil, false } + // subject alternate names (SANs) + if strings.HasPrefix(field, "client.san.") { + field = field[len("client.san."):] + var fieldName string + var fieldValue interface{} + switch { + case strings.HasPrefix(field, "dns_names"): + fieldName = "dns_names" + fieldValue = cert.DNSNames + case strings.HasPrefix(field, "emails"): + fieldName = "emails" + fieldValue = cert.EmailAddresses + case strings.HasPrefix(field, "ips"): + fieldName = "ips" + fieldValue = cert.IPAddresses + case strings.HasPrefix(field, "uris"): + fieldName = "uris" + fieldValue = cert.URIs + default: + return nil, false + } + field = field[len(fieldName):] + + // if no index was specified, return the whole list + if field == "" { + return fieldValue, true + } + if len(field) < 2 || field[0] != '.' { + return nil, false + } + field = field[1:] // trim '.' between field name and index + + // get the numeric index + idx, err := strconv.Atoi(field) + if err != nil || idx < 0 { + return nil, false + } + + // access the indexed element and return it + switch v := fieldValue.(type) { + case []string: + if idx >= len(v) { + return nil, true + } + return v[idx], true + case []net.IP: + if idx >= len(v) { + return nil, true + } + return v[idx], true + case []*url.URL: + if idx >= len(v) { + return nil, true + } + return v[idx], true + } + } + switch field { case "client.fingerprint": return fmt.Sprintf("%x", sha256.Sum256(cert.Raw)), true diff --git a/modules/caddyhttp/replacer_test.go b/modules/caddyhttp/replacer_test.go index ea9fa65..bc18c5a 100644 --- a/modules/caddyhttp/replacer_test.go +++ b/modules/caddyhttp/replacer_test.go @@ -147,6 +147,26 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV input: "{http.request.tls.client.subject}", expect: "CN=client.localdomain", }, + { + input: "{http.request.tls.client.san.dns_names}", + expect: "[localhost]", + }, + { + input: "{http.request.tls.client.san.dns_names.0}", + expect: "localhost", + }, + { + input: "{http.request.tls.client.san.dns_names.1}", + expect: "", + }, + { + input: "{http.request.tls.client.san.ips}", + expect: "[127.0.0.1]", + }, + { + input: "{http.request.tls.client.san.ips.0}", + expect: "127.0.0.1", + }, } { actual := repl.ReplaceAll(tc.input, "") if actual != tc.expect { -- cgit v1.2.3