forked from zmap/zlint
/
encodings.go
136 lines (124 loc) · 4.05 KB
/
encodings.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/*
* ZLint Copyright 2021 Regents of the University of Michigan
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package util
import (
"bytes"
"errors"
"regexp"
"strings"
"unicode"
"unicode/utf16"
"github.com/zmap/zcrypto/encoding/asn1"
"github.com/zmap/zcrypto/x509/pkix"
)
// CheckRDNSequenceWhiteSpace returns true if there is leading or trailing
// whitespace in any name attribute in the sequence, respectively.
func CheckRDNSequenceWhiteSpace(raw []byte) (leading, trailing bool, err error) {
var seq pkix.RDNSequence
if _, err = asn1.Unmarshal(raw, &seq); err != nil {
return
}
for _, rdn := range seq {
for _, atv := range rdn {
if !IsNameAttribute(atv.Type) {
continue
}
value, ok := atv.Value.(string)
if !ok {
continue
}
if leftStrip := strings.TrimLeftFunc(value, unicode.IsSpace); leftStrip != value {
leading = true
}
if rightStrip := strings.TrimRightFunc(value, unicode.IsSpace); rightStrip != value {
trailing = true
}
}
}
return
}
// IsIA5String returns true if raw is an IA5String, and returns false otherwise.
func IsIA5String(raw []byte) bool {
for _, b := range raw {
i := int(b)
if i > 127 || i < 0 {
return false
}
}
return true
}
func IsInPrefSyn(name string) bool {
// If the DNS name is just a space, it is valid
if name == " " {
return true
}
// 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
prefsyn := regexp.MustCompile(`^([[:alpha:]]{1}(([[:alnum:]]|[-])*[[:alnum:]]{1})*){1}([.][[:alpha:]]{1}(([[:alnum:]]|[-])*[[:alnum:]]{1})*)*$`)
return prefsyn.MatchString(name)
}
// AllAlternateNameWithTagAreIA5 returns true if all sequence members with the
// given tag are encoded as IA5 strings, and false otherwise. If it encounters
// errors parsing asn1, err will be non-nil.
func AllAlternateNameWithTagAreIA5(ext *pkix.Extension, tag int) (bool, error) {
var seq asn1.RawValue
var err error
// Unmarshal the extension as a sequence
if _, err = asn1.Unmarshal(ext.Value, &seq); err != nil {
return false, err
}
// Ensure the sequence matches what we expect for SAN/IAN
if !seq.IsCompound || seq.Tag != asn1.TagSequence || seq.Class != asn1.ClassUniversal {
err = asn1.StructuralError{Msg: "bad alternate name sequence"}
return false, err
}
// Iterate over the sequence and look for items tagged with tag
rest := seq.Bytes
for len(rest) > 0 {
var v asn1.RawValue
rest, err = asn1.Unmarshal(rest, &v)
if err != nil {
return false, err
}
if v.Tag == tag {
if !IsIA5String(v.Bytes) {
return false, nil
}
}
}
return true, nil
}
// IsEmptyASN1Sequence returns true if
// *input is an empty sequence (0x30, 0x00) or
// *len(inout) < 2
// This check covers more cases than just empty sequence checks but it makes sense from the usage perspective
var emptyASN1Sequence = []byte{0x30, 0x00}
func IsEmptyASN1Sequence(input []byte) bool {
return len(input) < 2 || bytes.Equal(input, emptyASN1Sequence)
}
// ParseBMPString returns a uint16 encoded string following the specification for a BMPString type
func ParseBMPString(bmpString []byte) (string, error) {
if len(bmpString)%2 != 0 {
return "", errors.New("odd-length BMP string")
}
// strip terminator if present
if l := len(bmpString); l >= 2 && bmpString[l-1] == 0 && bmpString[l-2] == 0 {
bmpString = bmpString[:l-2]
}
s := make([]uint16, 0, len(bmpString)/2)
for len(bmpString) > 0 {
s = append(s, uint16(bmpString[0])<<8+uint16(bmpString[1]))
bmpString = bmpString[2:]
}
return string(utf16.Decode(s)), nil
}