github.com/zmap/zlint@v1.1.0/lints/lint_subject_contains_reserved_arpa_ip.go (about) 1 /* 2 * ZLint Copyright 2019 Regents of the University of Michigan 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy 6 * of the License at http://www.apache.org/licenses/LICENSE-2.0 7 * 8 * Unless required by applicable law or agreed to in writing, software 9 * distributed under the License is distributed on an "AS IS" BASIS, 10 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 11 * implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package lints 16 17 import ( 18 "fmt" 19 "net" 20 "strings" 21 22 "github.com/zmap/zcrypto/x509" 23 "github.com/zmap/zlint/util" 24 ) 25 26 const ( 27 // arpaTLD holds a string constant for the .arpa TLD 28 arpaTLD = ".arpa" 29 30 // rdnsIPv4Suffix is the expected suffix for IPv4 reverse DNS names as 31 // specified in https://tools.ietf.org/html/rfc1035#section-3.5 32 rdnsIPv4Suffix = ".in-addr" + arpaTLD 33 // rndsIPv4Labels is the expected number of labels for an IPv4 reverse DNS 34 // name (not counting the rdnsIPv4Suffix labels). IPv4 addresses are four 35 // bytes. RFC 1035 uses one byte per label meaning there are 4 expected labels 36 // under the rdnsIPv4Suffix. 37 rdnsIPv4Labels = 4 38 39 // rdnsIPv6Suffix is the expected suffix for IPv6 reverse DNS names as 40 // specified in https://tools.ietf.org/html/rfc3596#section-2.5 41 rdnsIPv6Suffix = ".ip6" + arpaTLD 42 // rndsIPv6Labels is the expected number of labels for an IPv6 reverse DNS 43 // name (not counting the rdnsIPv6Suffix labels). IPv6 addresses are 16 bytes. 44 // RFC 3596 Sec 2.5 uses one *nibble* per label meaning there are 16*2 45 // expected labels under the rdnsIPv6Suffix. 46 rdnsIPv6Labels = 32 47 ) 48 49 // arpaReservedIP is a linter that errors for any well formed rDNS names in the 50 // .in-addr.arpa or .ip6.arpa zones that specify an address in an IANA reserved 51 // network. 52 // See also: lint_subject_contains_malformed_arpa_ip.go for a lint that warns 53 // about malformed rDNS names in these zones. 54 type arpaReservedIP struct{} 55 56 // Initialize for an arpaReservedIP linter is a NOP to statisfy linting 57 // interfaces. 58 func (l *arpaReservedIP) Initialize() error { 59 return nil 60 } 61 62 // CheckApplies returns true if the certificate contains any names that end in 63 // one of the two designated zones for reverse DNS: in-addr.arpa or ip6.arpa. 64 func (l *arpaReservedIP) CheckApplies(c *x509.Certificate) bool { 65 names := append([]string{c.Subject.CommonName}, c.DNSNames...) 66 for _, name := range names { 67 name = strings.ToLower(name) 68 if strings.HasSuffix(name, rdnsIPv4Suffix) || 69 strings.HasSuffix(name, rdnsIPv6Suffix) { 70 return true 71 } 72 } 73 return false 74 } 75 76 // Execute will check the given certificate to ensure that all of the DNS 77 // subject alternate names that specify a well formed reverse DNS name under the 78 // respective IPv4 or IPv6 arpa zones do not specify an IP in an IANA 79 // reserved IP space. An Error LintResult is returned if the name specifies an 80 // IP address of the wrong class, or specifies an IP address in an IANA reserved 81 // network. 82 func (l *arpaReservedIP) Execute(c *x509.Certificate) *LintResult { 83 for _, name := range c.DNSNames { 84 name = strings.ToLower(name) 85 var err error 86 if strings.HasSuffix(name, rdnsIPv4Suffix) { 87 // If the name has the in-addr.arpa suffix then it should be an IPv4 reverse 88 // DNS name. 89 err = lintReversedIPAddress(name, false) 90 } else if strings.HasSuffix(name, rdnsIPv6Suffix) { 91 // If the name has the ip6.arpa suffix then it should be an IPv6 reverse 92 // DNS name. 93 err = lintReversedIPAddress(name, true) 94 } 95 // Return the first error as a negative lint result 96 if err != nil { 97 return &LintResult{ 98 Status: Error, 99 Details: err.Error(), 100 } 101 } 102 } 103 104 return &LintResult{ 105 Status: Pass, 106 } 107 } 108 109 // reversedLabelsToIPv4 reverses the provided labels (assumed to be 4 labels, 110 // one per byte of the IPv6 address) and constructs an IPv4 address, returning 111 // the result of calling net.ParseIP for the constructed address. 112 func reversedLabelsToIPv4(labels []string) net.IP { 113 var buf strings.Builder 114 115 // If there aren't the right number of labels, it isn't an IPv4 address. 116 if len(labels) != rdnsIPv4Labels { 117 return nil 118 } 119 120 // An IPv4 address is represented as four groups of bytes separated by '.' 121 for i := len(labels) - 1; i >= 0; i-- { 122 buf.WriteString(labels[i]) 123 if i != 0 { 124 buf.WriteString(".") 125 } 126 } 127 return net.ParseIP(buf.String()) 128 } 129 130 // reversedLabelsToIPv6 reverses the provided labels (assumed to be 32 labels, 131 // one per nibble of an IPv6 address) and constructs an IPv6 address, returning 132 // the result of calling net.ParseIP for the constructed address. 133 func reversedLabelsToIPv6(labels []string) net.IP { 134 var buf strings.Builder 135 136 // If there aren't the right number of labels, it isn't an IPv6 address. 137 if len(labels) != rdnsIPv6Labels { 138 return nil 139 } 140 141 // An IPv6 address is represented as eight groups of two bytes separated 142 // by `:` in hex form. Since each label in the rDNS form is one nibble we need 143 // four label components per IPv6 address component group. 144 for i := len(labels) - 1; i >= 0; i -= 4 { 145 buf.WriteString(labels[i]) 146 buf.WriteString(labels[i-1]) 147 buf.WriteString(labels[i-2]) 148 buf.WriteString(labels[i-3]) 149 if i > 4 { 150 buf.WriteString(":") 151 } 152 } 153 return net.ParseIP(buf.String()) 154 } 155 156 // lintReversedIPAddress lints the given name as either a reversed IPv4 or IPv6 157 // address under the respective ARPA zone based on the address class. An error 158 // is returned if: 159 // 160 // 1. The IP address labels parse as an IP of the wrong address class for the 161 // arpa suffix the name is using. 162 // 2. The IP address is within an IANA reserved range. 163 func lintReversedIPAddress(name string, ipv6 bool) error { 164 numRequiredLabels := rdnsIPv4Labels 165 zoneSuffix := rdnsIPv4Suffix 166 167 if ipv6 { 168 numRequiredLabels = rdnsIPv6Labels 169 zoneSuffix = rdnsIPv6Suffix 170 } 171 172 // Strip off the zone suffix to get only the reversed IP address 173 ipName := strings.TrimSuffix(name, zoneSuffix) 174 175 // A well encoded IPv4 or IPv6 reverse DNS name will have the correct number 176 // of labels to express the address. If there isn't the right number of labels 177 // a separate `lint_subject_contains_malformed_arpa_ip.go` linter will flag it 178 // as a warning. This linter is specifically concerned with well formed rDNS 179 // that specifies a reserved IP. 180 ipLabels := strings.Split(ipName, ".") 181 if len(ipLabels) != numRequiredLabels { 182 return nil 183 } 184 185 // Reverse the IP labels and try to parse an IP address 186 var ip net.IP 187 if ipv6 { 188 ip = reversedLabelsToIPv6(ipLabels) 189 } else { 190 ip = reversedLabelsToIPv4(ipLabels) 191 } 192 // If the result isn't an IP at all assume there is no problem - leave 193 // `lint_subject_contains_malformed_arpa_ip` to flag it as a warning. 194 if ip == nil { 195 return nil 196 } 197 198 if !ipv6 && ip.To4() == nil { 199 // If we weren't expecting IPv6 and got it, that's a problem 200 return fmt.Errorf( 201 "the first %d labels of name %q parsed as a reversed IPv6 address but is "+ 202 "in the %q IPv4 reverse DNS zone.", 203 numRequiredLabels, name, rdnsIPv4Suffix) 204 } else if ipv6 && ip.To4() != nil { 205 // If we were expecting IPv6 and got an IPv4 address, that's a problem 206 return fmt.Errorf( 207 "the first %d labels of name %q parsed as a reversed IPv4 address but is "+ 208 "in the %q IPv4 reverse DNS zone.", 209 numRequiredLabels, name, rdnsIPv6Suffix) 210 } 211 212 // If the IP address is in an IANA reserved space, that's a problem. 213 if util.IsIANAReserved(ip) { 214 return fmt.Errorf( 215 "the first %d labels of name %q parsed as a reversed IP address in "+ 216 "an IANA reserved IP space.", 217 numRequiredLabels, name) 218 } 219 220 return nil 221 } 222 223 func init() { 224 RegisterLint(&Lint{ 225 Name: "e_subject_contains_reserved_arpa_ip", 226 Description: "Checks no subject domain name contains a rDNS entry in an .arpa zone specifying a reserved IP address", 227 Citation: "BRs: 7.1.4.2.1", 228 Source: CABFBaselineRequirements, 229 EffectiveDate: util.CABEffectiveDate, 230 Lint: &arpaReservedIP{}, 231 }) 232 }