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 }