verify_hostname.go (6145B)
1 // Source: https://git.sr.ht/~adnano/go-gemini/tree/f6b0443a6262d17f90b4e75cf5ae37577db7f897/vendor.go 2 // No code changes were made. 3 4 // Hostname verification code from the crypto/x509 package. 5 // Modified to allow Common Names in the short term, until new certificates 6 // can be issued with SANs. 7 8 // Copyright 2011 The Go Authors. All rights reserved. 9 // Use of this source code is governed by a BSD-style 10 // license that can be found in the LICENSE-GO file. 11 12 package gemini 13 14 import ( 15 "crypto/x509" 16 "net" 17 "strings" 18 "unicode/utf8" 19 ) 20 21 var oidExtensionSubjectAltName = []int{2, 5, 29, 17} 22 23 func hasSANExtension(c *x509.Certificate) bool { 24 for _, e := range c.Extensions { 25 if e.Id.Equal(oidExtensionSubjectAltName) { 26 return true 27 } 28 } 29 return false 30 } 31 32 func validHostnamePattern(host string) bool { return validHostname(host, true) } 33 func validHostnameInput(host string) bool { return validHostname(host, false) } 34 35 // validHostname reports whether host is a valid hostname that can be matched or 36 // matched against according to RFC 6125 2.2, with some leniency to accommodate 37 // legacy values. 38 func validHostname(host string, isPattern bool) bool { 39 if !isPattern { 40 host = strings.TrimSuffix(host, ".") 41 } 42 if len(host) == 0 { 43 return false 44 } 45 46 for i, part := range strings.Split(host, ".") { 47 if part == "" { 48 // Empty label. 49 return false 50 } 51 if isPattern && i == 0 && part == "*" { 52 // Only allow full left-most wildcards, as those are the only ones 53 // we match, and matching literal '*' characters is probably never 54 // the expected behavior. 55 continue 56 } 57 for j, c := range part { 58 if 'a' <= c && c <= 'z' { 59 continue 60 } 61 if '0' <= c && c <= '9' { 62 continue 63 } 64 if 'A' <= c && c <= 'Z' { 65 continue 66 } 67 if c == '-' && j != 0 { 68 continue 69 } 70 if c == '_' { 71 // Not a valid character in hostnames, but commonly 72 // found in deployments outside the WebPKI. 73 continue 74 } 75 return false 76 } 77 } 78 79 return true 80 } 81 82 // commonNameAsHostname reports whether the Common Name field should be 83 // considered the hostname that the certificate is valid for. This is a legacy 84 // behavior, disabled by default or if the Subject Alt Name extension is present. 85 // 86 // It applies the strict validHostname check to the Common Name field, so that 87 // certificates without SANs can still be validated against CAs with name 88 // constraints if there is no risk the CN would be matched as a hostname. 89 // See NameConstraintsWithoutSANs and issue 24151. 90 func commonNameAsHostname(c *x509.Certificate) bool { 91 return !hasSANExtension(c) && validHostnamePattern(c.Subject.CommonName) 92 } 93 94 func matchExactly(hostA, hostB string) bool { 95 if hostA == "" || hostA == "." || hostB == "" || hostB == "." { 96 return false 97 } 98 return toLowerCaseASCII(hostA) == toLowerCaseASCII(hostB) 99 } 100 101 func matchHostnames(pattern, host string) bool { 102 pattern = toLowerCaseASCII(pattern) 103 host = toLowerCaseASCII(strings.TrimSuffix(host, ".")) 104 105 if len(pattern) == 0 || len(host) == 0 { 106 return false 107 } 108 109 patternParts := strings.Split(pattern, ".") 110 hostParts := strings.Split(host, ".") 111 112 if len(patternParts) != len(hostParts) { 113 return false 114 } 115 116 for i, patternPart := range patternParts { 117 if i == 0 && patternPart == "*" { 118 continue 119 } 120 if patternPart != hostParts[i] { 121 return false 122 } 123 } 124 125 return true 126 } 127 128 // toLowerCaseASCII returns a lower-case version of in. See RFC 6125 6.4.1. We use 129 // an explicitly ASCII function to avoid any sharp corners resulting from 130 // performing Unicode operations on DNS labels. 131 func toLowerCaseASCII(in string) string { 132 // If the string is already lower-case then there's nothing to do. 133 isAlreadyLowerCase := true 134 for _, c := range in { 135 if c == utf8.RuneError { 136 // If we get a UTF-8 error then there might be 137 // upper-case ASCII bytes in the invalid sequence. 138 isAlreadyLowerCase = false 139 break 140 } 141 if 'A' <= c && c <= 'Z' { 142 isAlreadyLowerCase = false 143 break 144 } 145 } 146 147 if isAlreadyLowerCase { 148 return in 149 } 150 151 out := []byte(in) 152 for i, c := range out { 153 if 'A' <= c && c <= 'Z' { 154 out[i] += 'a' - 'A' 155 } 156 } 157 return string(out) 158 } 159 160 // verifyHostname returns nil if c is a valid certificate for the named host. 161 // Otherwise it returns an error describing the mismatch. 162 // 163 // IP addresses can be optionally enclosed in square brackets and are checked 164 // against the IPAddresses field. Other names are checked case insensitively 165 // against the DNSNames field. If the names are valid hostnames, the certificate 166 // fields can have a wildcard as the left-most label. 167 // 168 // The legacy Common Name field is ignored unless it's a valid hostname, the 169 // certificate doesn't have any Subject Alternative Names, and the GODEBUG 170 // environment variable is set to "x509ignoreCN=0". Support for Common Name is 171 // deprecated will be entirely removed in the future. 172 func verifyHostname(c *x509.Certificate, h string) error { 173 // IP addresses may be written in [ ]. 174 candidateIP := h 175 if len(h) >= 3 && h[0] == '[' && h[len(h)-1] == ']' { 176 candidateIP = h[1 : len(h)-1] 177 } 178 if ip := net.ParseIP(candidateIP); ip != nil { 179 // We only match IP addresses against IP SANs. 180 // See RFC 6125, Appendix B.2. 181 for _, candidate := range c.IPAddresses { 182 if ip.Equal(candidate) { 183 return nil 184 } 185 } 186 return x509.HostnameError{c, candidateIP} 187 } 188 189 names := c.DNSNames 190 if commonNameAsHostname(c) { 191 names = []string{c.Subject.CommonName} 192 } 193 194 candidateName := toLowerCaseASCII(h) // Save allocations inside the loop. 195 validCandidateName := validHostnameInput(candidateName) 196 197 for _, match := range names { 198 // Ideally, we'd only match valid hostnames according to RFC 6125 like 199 // browsers (more or less) do, but in practice Go is used in a wider 200 // array of contexts and can't even assume DNS resolution. Instead, 201 // always allow perfect matches, and only apply wildcard and trailing 202 // dot processing to valid hostnames. 203 if validCandidateName && validHostnamePattern(match) { 204 if matchHostnames(match, candidateName) { 205 return nil 206 } 207 } else { 208 if matchExactly(match, candidateName) { 209 return nil 210 } 211 } 212 } 213 214 return x509.HostnameError{c, h} 215 }