github.com/zmap/zlint@v1.1.0/lints/lint_subject_contains_malformed_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  // arpaMalformedIP is a linter that warns for malformed names under the
    27  // .in-addr.arpa or .ip6.arpa zones.
    28  // See also: lint_subject_contains_reserved_arpa_ip.go for a lint that ensures
    29  // well formed rDNS names in these zones do not specify an address in a IANA
    30  // reserved network.
    31  type arpaMalformedIP struct{}
    32  
    33  // Initialize for an arpaMalformedIP linter is a NOP to statisfy linting
    34  // interfaces.
    35  func (l *arpaMalformedIP) Initialize() error {
    36  	return nil
    37  }
    38  
    39  // CheckApplies returns true if the certificate contains any names that end in
    40  // one of the two designated zones for reverse DNS: in-addr.arpa or ip6.arpa.
    41  func (l *arpaMalformedIP) CheckApplies(c *x509.Certificate) bool {
    42  	names := append([]string{c.Subject.CommonName}, c.DNSNames...)
    43  	for _, name := range names {
    44  		name = strings.ToLower(name)
    45  		if strings.HasSuffix(name, rdnsIPv4Suffix) ||
    46  			strings.HasSuffix(name, rdnsIPv6Suffix) {
    47  			return true
    48  		}
    49  	}
    50  	return false
    51  }
    52  
    53  // Execute will check the given certificate to ensure that all of the DNS
    54  // subject alternate names that specify a reverse DNS name under the respective
    55  // IPv4 or IPv6 arpa zones are well formed. A Warn LintResult is returned if
    56  // the name is in a reverse DNS zone but has the wrong number of labels.
    57  func (l *arpaMalformedIP) Execute(c *x509.Certificate) *LintResult {
    58  	for _, name := range c.DNSNames {
    59  		name = strings.ToLower(name)
    60  		var err error
    61  		if strings.HasSuffix(name, rdnsIPv4Suffix) {
    62  			// If the name has the in-addr.arpa suffix then it should be an IPv4 reverse
    63  			// DNS name.
    64  			err = lintReversedIPAddressLabels(name, false)
    65  		} else if strings.HasSuffix(name, rdnsIPv6Suffix) {
    66  			// If the name has the ip6.arpa suffix then it should be an IPv6 reverse
    67  			// DNS name.
    68  			err = lintReversedIPAddressLabels(name, true)
    69  		}
    70  		// Return the first error as a negative lint result
    71  		if err != nil {
    72  			return &LintResult{
    73  				Status:  Warn,
    74  				Details: err.Error(),
    75  			}
    76  		}
    77  	}
    78  
    79  	return &LintResult{
    80  		Status: Pass,
    81  	}
    82  }
    83  
    84  // lintReversedIPAddressLabels lints the given name as either a reversed IPv4 or
    85  // IPv6 address under the respective ARPA zone based on the address class. An
    86  // error is returned if there aren't enough labels in the name after removing
    87  // the relevant arpa suffix.
    88  func lintReversedIPAddressLabels(name string, ipv6 bool) error {
    89  	numRequiredLabels := rdnsIPv4Labels
    90  	zoneSuffix := rdnsIPv4Suffix
    91  
    92  	if ipv6 {
    93  		numRequiredLabels = rdnsIPv6Labels
    94  		zoneSuffix = rdnsIPv6Suffix
    95  	}
    96  
    97  	// Strip off the zone suffix to get only the reversed IP address
    98  	ipName := strings.TrimSuffix(name, zoneSuffix)
    99  
   100  	// A well encoded IPv4 or IPv6 reverse DNS name will have the correct number
   101  	// of labels to express the address
   102  	ipLabels := strings.Split(ipName, ".")
   103  	if len(ipLabels) != numRequiredLabels {
   104  		return fmt.Errorf(
   105  			"name %q has too few leading labels (%d vs %d) to be a reverse DNS entry "+
   106  				"in the %q zone.",
   107  			name, len(ipLabels), numRequiredLabels, zoneSuffix)
   108  	}
   109  
   110  	// Reverse the IP labels and try to parse an IP address
   111  	var ip net.IP
   112  	if ipv6 {
   113  		ip = reversedLabelsToIPv6(ipLabels)
   114  	} else {
   115  		ip = reversedLabelsToIPv4(ipLabels)
   116  	}
   117  
   118  	// If the result isn't an IP then a warning should be generated
   119  	if ip == nil {
   120  		return fmt.Errorf(
   121  			"the first %d labels of name %q did not parse as a reversed IP address",
   122  			numRequiredLabels, name)
   123  	}
   124  
   125  	// Otherwise return no error - checking the actual value of the IP is left to
   126  	// `lint_subject_contains_reserved_arpa_ip.go`.
   127  	return nil
   128  }
   129  
   130  func init() {
   131  	RegisterLint(&Lint{
   132  		Name:          "w_subject_contains_malformed_arpa_ip",
   133  		Description:   "Checks no subject domain name contains a rDNS entry in an .arpa zone with the wrong number of labels",
   134  		Citation:      "BRs: 7.1.4.2.1",
   135  		Source:        CABFBaselineRequirements,
   136  		EffectiveDate: util.CABEffectiveDate,
   137  		Lint:          &arpaMalformedIP{},
   138  	})
   139  }