// Copyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com) // Portions copyright (c) 2015-2016 go-ldap Authors package ldap import ( "bytes" "crypto/x509/pkix" "encoding/asn1" enchex "encoding/hex" "errors" "fmt" "strings" ) var attributeTypeNames = map[string]string{ "2.5.4.3": "CN", "2.5.4.5": "SERIALNUMBER", "2.5.4.6": "C", "2.5.4.7": "L", "2.5.4.8": "ST", "2.5.4.9": "STREET", "2.5.4.10": "O", "2.5.4.11": "OU", "2.5.4.17": "POSTALCODE", // FIXME: Add others. "0.9.2342.19200300.100.1.25": "DC", } // AttributeTypeAndValue represents an attributeTypeAndValue from https://tools.ietf.org/html/rfc4514 type AttributeTypeAndValue struct { // Type is the attribute type Type string // Value is the attribute value Value string } // RelativeDN represents a relativeDistinguishedName from https://tools.ietf.org/html/rfc4514 type RelativeDN struct { Attributes []*AttributeTypeAndValue } // DN represents a distinguishedName from https://tools.ietf.org/html/rfc4514 type DN struct { RDNs []*RelativeDN } // FromCertSubject takes a pkix.Name from a cert and returns a DN // that uses the same set. Does not support multi value RDNs. func FromCertSubject(subject pkix.Name) (*DN, error) { dn := &DN{ RDNs: make([]*RelativeDN, 0), } for i := len(subject.Names) - 1; i >= 0; i-- { name := subject.Names[i] oidString := name.Type.String() typeName, ok := attributeTypeNames[oidString] if !ok { return nil, fmt.Errorf("invalid type name: %+v", name) } v, ok := name.Value.(string) if !ok { return nil, fmt.Errorf("invalid type value: %+v", v) } rdn := &RelativeDN{ Attributes: []*AttributeTypeAndValue{ { Type: typeName, Value: v, }, }, } dn.RDNs = append(dn.RDNs, rdn) } return dn, nil } // FromRawCertSubject takes a raw subject from a certificate // and uses asn1.Unmarshal to get the individual RDNs in the // original order, including multi-value RDNs. func FromRawCertSubject(rawSubject []byte) (*DN, error) { dn := &DN{ RDNs: make([]*RelativeDN, 0), } var rdns pkix.RDNSequence _, err := asn1.Unmarshal(rawSubject, &rdns) if err != nil { return nil, err } for i := len(rdns) - 1; i >= 0; i-- { rdn := rdns[i] if len(rdn) == 0 { continue } r := &RelativeDN{} attrs := make([]*AttributeTypeAndValue, 0) for j := len(rdn) - 1; j >= 0; j-- { atv := rdn[j] typeName := "" name := atv.Type.String() typeName, ok := attributeTypeNames[name] if !ok { return nil, fmt.Errorf("invalid type name: %+v", name) } value, ok := atv.Value.(string) if !ok { return nil, fmt.Errorf("invalid type value: %+v", atv.Value) } attr := &AttributeTypeAndValue{ Type: typeName, Value: value, } attrs = append(attrs, attr) } r.Attributes = attrs dn.RDNs = append(dn.RDNs, r) } return dn, nil } // ParseDN returns a distinguishedName or an error. // The function respects https://tools.ietf.org/html/rfc4514 func ParseDN(str string) (*DN, error) { dn := new(DN) dn.RDNs = make([]*RelativeDN, 0) rdn := new(RelativeDN) rdn.Attributes = make([]*AttributeTypeAndValue, 0) buffer := bytes.Buffer{} attribute := new(AttributeTypeAndValue) escaping := false unescapedTrailingSpaces := 0 stringFromBuffer := func() string { s := buffer.String() s = s[0 : len(s)-unescapedTrailingSpaces] buffer.Reset() unescapedTrailingSpaces = 0 return s } for i := 0; i < len(str); i++ { char := str[i] switch { case escaping: unescapedTrailingSpaces = 0 escaping = false switch char { case ' ', '"', '#', '+', ',', ';', '<', '=', '>', '\\': buffer.WriteByte(char) continue } // Not a special character, assume hex encoded octet if len(str) == i+1 { return nil, errors.New("got corrupted escaped character") } dst := []byte{0} n, err := enchex.Decode([]byte(dst), []byte(str[i:i+2])) if err != nil { return nil, fmt.Errorf("failed to decode escaped character: %s", err) } else if n != 1 { return nil, fmt.Errorf("expected 1 byte when un-escaping, got %d", n) } buffer.WriteByte(dst[0]) i++ case char == '\\': unescapedTrailingSpaces = 0 escaping = true case char == '=': attribute.Type = stringFromBuffer() // Special case: If the first character in the value is # the following data // is BER encoded. Throw an error since not supported right now. if len(str) > i+1 && str[i+1] == '#' { return nil, errors.New("unsupported BER encoding") } case char == ',' || char == '+': // We're done with this RDN or value, push it if len(attribute.Type) == 0 { return nil, errors.New("incomplete type, value pair") } attribute.Value = stringFromBuffer() rdn.Attributes = append(rdn.Attributes, attribute) attribute = new(AttributeTypeAndValue) if char == ',' { dn.RDNs = append(dn.RDNs, rdn) rdn = new(RelativeDN) rdn.Attributes = make([]*AttributeTypeAndValue, 0) } case char == ' ' && buffer.Len() == 0: // ignore unescaped leading spaces continue default: if char == ' ' { // Track unescaped spaces in case they are trailing and we need to remove them unescapedTrailingSpaces++ } else { // Reset if we see a non-space char unescapedTrailingSpaces = 0 } buffer.WriteByte(char) } } if buffer.Len() > 0 { if len(attribute.Type) == 0 { return nil, errors.New("DN ended with incomplete type, value pair") } attribute.Value = stringFromBuffer() rdn.Attributes = append(rdn.Attributes, attribute) dn.RDNs = append(dn.RDNs, rdn) } return dn, nil } // Equal returns true if the DNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch). // Returns true if they have the same number of relative distinguished names // and corresponding relative distinguished names (by position) are the same. func (d *DN) Equal(other *DN) bool { if len(d.RDNs) != len(other.RDNs) { return false } for i := range d.RDNs { if !d.RDNs[i].Equal(other.RDNs[i]) { return false } } return true } // RDNsMatch returns true if the individual RDNs of the DNs // are the same regardless of ordering. func (d *DN) RDNsMatch(other *DN) bool { if len(d.RDNs) != len(other.RDNs) { return false } CheckNextRDN: for _, irdn := range d.RDNs { for _, ordn := range other.RDNs { if (len(irdn.Attributes) == len(ordn.Attributes)) && (irdn.hasAllAttributes(ordn.Attributes) && ordn.hasAllAttributes(irdn.Attributes)) { // Found the RDN, check if next one matches. continue CheckNextRDN } } // Could not find a matching individual RDN, auth fails. return false } return true } // AncestorOf returns true if the other DN consists of at least one RDN followed by all the RDNs of the current DN. // "ou=widgets,o=acme.com" is an ancestor of "ou=sprockets,ou=widgets,o=acme.com" // "ou=widgets,o=acme.com" is not an ancestor of "ou=sprockets,ou=widgets,o=foo.com" // "ou=widgets,o=acme.com" is not an ancestor of "ou=widgets,o=acme.com" func (d *DN) AncestorOf(other *DN) bool { if len(d.RDNs) >= len(other.RDNs) { return false } // Take the last `len(d.RDNs)` RDNs from the other DN to compare against otherRDNs := other.RDNs[len(other.RDNs)-len(d.RDNs):] for i := range d.RDNs { if !d.RDNs[i].Equal(otherRDNs[i]) { return false } } return true } // Equal returns true if the RelativeDNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch). // Relative distinguished names are the same if and only if they have the same number of AttributeTypeAndValues // and each attribute of the first RDN is the same as the attribute of the second RDN with the same attribute type. // The order of attributes is not significant. // Case of attribute types is not significant. func (r *RelativeDN) Equal(other *RelativeDN) bool { if len(r.Attributes) != len(other.Attributes) { return false } return r.hasAllAttributes(other.Attributes) && other.hasAllAttributes(r.Attributes) } func (r *RelativeDN) hasAllAttributes(attrs []*AttributeTypeAndValue) bool { for _, attr := range attrs { found := false for _, myattr := range r.Attributes { if myattr.Equal(attr) { found = true break } } if !found { return false } } return true } // Equal returns true if the AttributeTypeAndValue is equivalent to the specified AttributeTypeAndValue // Case of the attribute type is not significant func (a *AttributeTypeAndValue) Equal(other *AttributeTypeAndValue) bool { return strings.EqualFold(a.Type, other.Type) && a.Value == other.Value }