Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add String() methods to DN and its subtypes #386

Merged
merged 2 commits into from
Jul 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
80 changes: 80 additions & 0 deletions dn.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
enchex "encoding/hex"
"errors"
"fmt"
"sort"
"strings"

ber "github.com/go-asn1-ber/asn1-ber"
Expand All @@ -18,16 +19,95 @@ type AttributeTypeAndValue struct {
Value string
}

// String returns a normalized string representation of this attribute type and
// value pair which is the a lowercased join of the Type and Value with a "=".
func (a *AttributeTypeAndValue) String() string {
return strings.ToLower(a.Type) + "=" + a.encodeValue()
}

func (a *AttributeTypeAndValue) encodeValue() string {
// Normalize the value first.
// value := strings.ToLower(a.Value)
value := a.Value

encodedBuf := bytes.Buffer{}

escapeChar := func(c byte) {
encodedBuf.WriteByte('\\')
encodedBuf.WriteByte(c)
}

escapeHex := func(c byte) {
encodedBuf.WriteByte('\\')
encodedBuf.WriteString(enchex.EncodeToString([]byte{c}))
}

for i := 0; i < len(value); i++ {
char := value[i]
if i == 0 && char == ' ' || char == '#' {
// Special case leading space or number sign.
escapeChar(char)
continue
}
if i == len(value)-1 && char == ' ' {
// Special case trailing space.
escapeChar(char)
continue
}

switch char {
case '"', '+', ',', ';', '<', '>', '\\':
// Each of these special characters must be escaped.
escapeChar(char)
continue
}

if char < ' ' || char > '~' {
// All special character escapes are handled first
// above. All bytes less than ASCII SPACE and all bytes
// greater than ASCII TILDE must be hex-escaped.
escapeHex(char)
continue
}

// Any other character does not require escaping.
encodedBuf.WriteByte(char)
}

return encodedBuf.String()
}

// RelativeDN represents a relativeDistinguishedName from https://tools.ietf.org/html/rfc4514
type RelativeDN struct {
Attributes []*AttributeTypeAndValue
}

// String returns a normalized string representation of this relative DN which
// is the a join of all attributes (sorted in increasing order) with a "+".
func (r *RelativeDN) String() string {
attrs := make([]string, len(r.Attributes))
for i := range r.Attributes {
attrs[i] = r.Attributes[i].String()
}
sort.Strings(attrs)
return strings.Join(attrs, "+")
}

// DN represents a distinguishedName from https://tools.ietf.org/html/rfc4514
type DN struct {
RDNs []*RelativeDN
}

// String returns a normalized string representation of this DN which is the
// join of all relative DNs with a ",".
func (d *DN) String() string {
rdns := make([]string, len(d.RDNs))
for i := range d.RDNs {
rdns[i] = d.RDNs[i].String()
}
return strings.Join(rdns, ",")
}

// ParseDN returns a distinguishedName or an error.
// The function respects https://tools.ietf.org/html/rfc4514
func ParseDN(str string) (*DN, error) {
Expand Down
13 changes: 9 additions & 4 deletions dn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ func TestDNEqual(t *testing.T) {
},
// Difference in leading/trailing chars is ignored
{
"cn=John Doe, ou=People, dc=sun.com",
"cn=John Doe,ou=People,dc=sun.com",
"cn=\\ John\\20Doe, ou=People, dc=sun.com",
"cn= \\ John Doe,ou=People,dc=sun.com",
true,
},
// Difference in values is significant
Expand All @@ -174,11 +174,16 @@ func TestDNEqual(t *testing.T) {
continue
}
if expected, actual := tc.Equal, a.Equal(b); expected != actual {
t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual)
t.Errorf("%d: when comparing %q and %q expected %v, got %v", i, a, b, expected, actual)
continue
}
if expected, actual := tc.Equal, b.Equal(a); expected != actual {
t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual)
t.Errorf("%d: when comparing %q and %q expected %v, got %v", i, a, b, expected, actual)
continue
}

if expected, actual := a.Equal(b), a.String() == b.String(); expected != actual {
t.Errorf("%d: when asserting string comparison of %q and %q expected equal %v, got %v", i, a, b, expected, actual)
continue
}
}
Expand Down
82 changes: 81 additions & 1 deletion v3/dn.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
enchex "encoding/hex"
"errors"
"fmt"
"sort"
"strings"

ber "github.com/go-asn1-ber/asn1-ber"
Expand All @@ -18,16 +19,95 @@ type AttributeTypeAndValue struct {
Value string
}

// String returns a normalized string representation of this attribute type and
// value pair which is the a lowercased join of the Type and Value with a "=".
func (a *AttributeTypeAndValue) String() string {
return strings.ToLower(a.Type) + "=" + a.encodeValue()
}

func (a *AttributeTypeAndValue) encodeValue() string {
// Normalize the value first.
// value := strings.ToLower(a.Value)
value := a.Value

encodedBuf := bytes.Buffer{}

escapeChar := func(c byte) {
encodedBuf.WriteByte('\\')
encodedBuf.WriteByte(c)
}

escapeHex := func(c byte) {
encodedBuf.WriteByte('\\')
encodedBuf.WriteString(enchex.EncodeToString([]byte{c}))
}

for i := 0; i < len(value); i++ {
char := value[i]
if i == 0 && char == ' ' || char == '#' {
// Special case leading space or number sign.
escapeChar(char)
continue
}
if i == len(value)-1 && char == ' ' {
// Special case trailing space.
escapeChar(char)
continue
}

switch char {
case '"', '+', ',', ';', '<', '>', '\\':
// Each of these special characters must be escaped.
escapeChar(char)
continue
}

if char < ' ' || char > '~' {
// All special character escapes are handled first
// above. All bytes less than ASCII SPACE and all bytes
// greater than ASCII TILDE must be hex-escaped.
escapeHex(char)
continue
}

// Any other character does not require escaping.
encodedBuf.WriteByte(char)
}

return encodedBuf.String()
}

// RelativeDN represents a relativeDistinguishedName from https://tools.ietf.org/html/rfc4514
type RelativeDN struct {
Attributes []*AttributeTypeAndValue
}

// String returns a normalized string representation of this relative DN which
// is the a join of all attributes (sorted in increasing order) with a "+".
func (r *RelativeDN) String() string {
attrs := make([]string, len(r.Attributes))
for i := range r.Attributes {
attrs[i] = r.Attributes[i].String()
}
sort.Strings(attrs)
return strings.Join(attrs, "+")
}

// DN represents a distinguishedName from https://tools.ietf.org/html/rfc4514
type DN struct {
RDNs []*RelativeDN
}

// String returns a normalized string representation of this DN which is the
// join of all relative DNs with a ",".
func (d *DN) String() string {
rdns := make([]string, len(d.RDNs))
for i := range d.RDNs {
rdns[i] = d.RDNs[i].String()
}
return strings.Join(rdns, ",")
}

// ParseDN returns a distinguishedName or an error.
// The function respects https://tools.ietf.org/html/rfc4514
func ParseDN(str string) (*DN, error) {
Expand Down Expand Up @@ -84,7 +164,7 @@ func ParseDN(str string) (*DN, error) {
if len(str) > i+1 && str[i+1] == '#' {
i += 2
index := strings.IndexAny(str[i:], ",+")
data := str
var data string
if index > 0 {
data = str[i : i+index]
} else {
Expand Down
13 changes: 9 additions & 4 deletions v3/dn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ func TestDNEqual(t *testing.T) {
},
// Difference in leading/trailing chars is ignored
{
"cn=John Doe, ou=People, dc=sun.com",
"cn=John Doe,ou=People,dc=sun.com",
"cn=\\ John\\20Doe, ou=People, dc=sun.com",
"cn= \\ John Doe,ou=People,dc=sun.com",
true,
},
// Difference in values is significant
Expand All @@ -174,11 +174,16 @@ func TestDNEqual(t *testing.T) {
continue
}
if expected, actual := tc.Equal, a.Equal(b); expected != actual {
t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual)
t.Errorf("%d: when comparing %q and %q expected %v, got %v", i, a, b, expected, actual)
continue
}
if expected, actual := tc.Equal, b.Equal(a); expected != actual {
t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual)
t.Errorf("%d: when comparing %q and %q expected %v, got %v", i, a, b, expected, actual)
continue
}

if expected, actual := a.Equal(b), a.String() == b.String(); expected != actual {
t.Errorf("%d: when asserting string comparison of %q and %q expected equal %v, got %v", i, a, b, expected, actual)
continue
}
}
Expand Down