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  }