github.com/zmap/zlint@v1.1.0/util/encodings.go (about)

     1  /*
     2   * ZLint Copyright 2018 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 util
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/asn1"
    20  	"errors"
    21  	"regexp"
    22  	"strings"
    23  	"unicode"
    24  	"unicode/utf16"
    25  
    26  	"github.com/zmap/zcrypto/x509/pkix"
    27  )
    28  
    29  // CheckRDNSequenceWhiteSpace returns true if there is leading or trailing
    30  // whitespace in any name attribute in the sequence, respectively.
    31  func CheckRDNSequenceWhiteSpace(raw []byte) (leading, trailing bool, err error) {
    32  	var seq pkix.RDNSequence
    33  	if _, err = asn1.Unmarshal(raw, &seq); err != nil {
    34  		return
    35  	}
    36  	for _, rdn := range seq {
    37  		for _, atv := range rdn {
    38  			if !IsNameAttribute(atv.Type) {
    39  				continue
    40  			}
    41  			value, ok := atv.Value.(string)
    42  			if !ok {
    43  				continue
    44  			}
    45  			if leftStrip := strings.TrimLeftFunc(value, unicode.IsSpace); leftStrip != value {
    46  				leading = true
    47  			}
    48  			if rightStrip := strings.TrimRightFunc(value, unicode.IsSpace); rightStrip != value {
    49  				trailing = true
    50  			}
    51  		}
    52  	}
    53  	return
    54  }
    55  
    56  // IsIA5String returns true if raw is an IA5String, and returns false otherwise.
    57  func IsIA5String(raw []byte) bool {
    58  	for _, b := range raw {
    59  		i := int(b)
    60  		if i > 127 || i < 0 {
    61  			return false
    62  		}
    63  	}
    64  	return true
    65  }
    66  
    67  func IsInPrefSyn(name string) bool {
    68  	// If the DNS name is just a space, it is valid
    69  	if name == " " {
    70  		return true
    71  	}
    72  	// This is the expression that matches the ABNF syntax from RFC 1034: Sec 3.5, specifically for subdomain since the " " case for domain is covered above
    73  	prefsyn := regexp.MustCompile(`^([[:alpha:]]{1}(([[:alnum:]]|[-])*[[:alnum:]]{1})*){1}([.][[:alpha:]]{1}(([[:alnum:]]|[-])*[[:alnum:]]{1})*)*$`)
    74  	return prefsyn.MatchString(name)
    75  }
    76  
    77  // AllAlternateNameWithTagAreIA5 returns true if all sequence members with the
    78  // given tag are encoded as IA5 strings, and false otherwise. If it encounters
    79  // errors parsing asn1, err will be non-nil.
    80  func AllAlternateNameWithTagAreIA5(ext *pkix.Extension, tag int) (bool, error) {
    81  	var seq asn1.RawValue
    82  	var err error
    83  	// Unmarshal the extension as a sequence
    84  	if _, err = asn1.Unmarshal(ext.Value, &seq); err != nil {
    85  		return false, err
    86  	}
    87  	// Ensure the sequence matches what we expect for SAN/IAN
    88  	if !seq.IsCompound || seq.Tag != asn1.TagSequence || seq.Class != asn1.ClassUniversal {
    89  		err = asn1.StructuralError{Msg: "bad alternate name sequence"}
    90  		return false, err
    91  	}
    92  
    93  	// Iterate over the sequence and look for items tagged with tag
    94  	rest := seq.Bytes
    95  	for len(rest) > 0 {
    96  		var v asn1.RawValue
    97  		rest, err = asn1.Unmarshal(rest, &v)
    98  		if err != nil {
    99  			return false, err
   100  		}
   101  		if v.Tag == tag {
   102  			if !IsIA5String(v.Bytes) {
   103  				return false, nil
   104  			}
   105  		}
   106  	}
   107  
   108  	return true, nil
   109  }
   110  
   111  // IsEmptyASN1Sequence returns true if
   112  // *input is an empty sequence (0x30, 0x00) or
   113  // *len(inout) < 2
   114  // This check covers more cases than just empty sequence checks but it makes sense from the usage perspective
   115  var emptyASN1Sequence = []byte{0x30, 0x00}
   116  
   117  func IsEmptyASN1Sequence(input []byte) bool {
   118  	return len(input) < 2 || bytes.Equal(input, emptyASN1Sequence)
   119  }
   120  
   121  // ParseBMPString returns a uint16 encoded string following the specification for a BMPString type
   122  func ParseBMPString(bmpString []byte) (string, error) {
   123  	if len(bmpString)%2 != 0 {
   124  		return "", errors.New("odd-length BMP string")
   125  	}
   126  	// strip terminator if present
   127  	if l := len(bmpString); l >= 2 && bmpString[l-1] == 0 && bmpString[l-2] == 0 {
   128  		bmpString = bmpString[:l-2]
   129  	}
   130  	s := make([]uint16, 0, len(bmpString)/2)
   131  	for len(bmpString) > 0 {
   132  		s = append(s, uint16(bmpString[0])<<8+uint16(bmpString[1]))
   133  		bmpString = bmpString[2:]
   134  	}
   135  	return string(utf16.Decode(s)), nil
   136  }