From 02b6c5ee51ef49738f235158e1228f1e116b75f3 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Thu, 12 Sep 2019 11:36:08 -0700 Subject: [PATCH] Add functions for canonicalizing an RDNSequence --- canonicalize.go | 92 ++++++++++++++++++++++++++++++++++++++++++++ canonicalize_test.go | 49 +++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 canonicalize.go create mode 100644 canonicalize_test.go diff --git a/canonicalize.go b/canonicalize.go new file mode 100644 index 0000000..57f9814 --- /dev/null +++ b/canonicalize.go @@ -0,0 +1,92 @@ +// Copyright (C) 2019 Opsmate, Inc. +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License, v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// This software is distributed WITHOUT A WARRANTY OF ANY KIND. +// See the Mozilla Public License for details. + +package certspotter + +import ( + "encoding/asn1" +) + +func canonicalizeRDNString(fromStr string) string { + from := []byte(fromStr) + to := []byte{} + inWhitespace := true + for _, ch := range from { + if ch == ' ' || ch == '\f' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\v' { + if !inWhitespace { + to = append(to, ' ') + } + inWhitespace = true + } else { + if ch >= 'A' && ch <= 'Z' { + to = append(to, ch+32) // convert to lowercase + } else { + to = append(to, ch) + } + inWhitespace = false + } + } + if inWhitespace && len(to) > 0 { + // whack off the space character that we appended + to = to[:len(to)-1] + } + return string(to) +} + +func shouldCanonicalizeASN1String(value *asn1.RawValue) bool { + if !value.IsCompound && value.Class == 0 { + return value.Tag == 12 || value.Tag == 19 || value.Tag == 22 || value.Tag == 20 || value.Tag == 26 || value.Tag == 30 || value.Tag == 28 + } + return false +} + +func canonicalizeATV(oldATV AttributeTypeAndValue) (AttributeTypeAndValue, error) { + if shouldCanonicalizeASN1String(&oldATV.Value) { + str, err := decodeASN1String(&oldATV.Value) + if err != nil { + return AttributeTypeAndValue{}, err + } + str = canonicalizeRDNString(str) + return AttributeTypeAndValue{ + Type: oldATV.Type, + Value: asn1.RawValue{ + Class: 0, + Tag: asn1.TagUTF8String, + IsCompound: false, + Bytes: []byte(str), + }, + }, nil + } else { + return oldATV, nil + } +} + +func canonicalizeRDNSet(oldSet RelativeDistinguishedNameSET) (RelativeDistinguishedNameSET, error) { + newSet := make([]AttributeTypeAndValue, len(oldSet)) + for i := range oldSet { + var err error + newSet[i], err = canonicalizeATV(oldSet[i]) + if err != nil { + return nil, err + } + } + return newSet, nil +} + +func CanonicalizeRDNSequence(oldSequence RDNSequence) (RDNSequence, error) { + newSequence := make([]RelativeDistinguishedNameSET, len(oldSequence)) + for i := range oldSequence { + var err error + newSequence[i], err = canonicalizeRDNSet(oldSequence[i]) + if err != nil { + return nil, err + } + } + return newSequence, nil +} diff --git a/canonicalize_test.go b/canonicalize_test.go new file mode 100644 index 0000000..134ba5d --- /dev/null +++ b/canonicalize_test.go @@ -0,0 +1,49 @@ +// Copyright (C) 2019 Opsmate, Inc. +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License, v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// This software is distributed WITHOUT A WARRANTY OF ANY KIND. +// See the Mozilla Public License for details. + +package certspotter + +import ( + "testing" +) + +type stringCanonTest struct { + in string + out string +} + +var stringCanonTests = []stringCanonTest{ + {"", ""}, + {" ", ""}, + {" ", ""}, + {"abc", "abc"}, + {"aBc", "abc"}, + {"ab c", "ab c"}, + {"ab c", "ab c"}, + {"ab\n c", "ab c"}, + {" ab c ", "ab c"}, + {" ab c ", "ab c"}, + {" ab c", "ab c"}, + {"ab c ", "ab c"}, + {"abc ", "abc"}, + {"abc ", "abc"}, + {" abc ", "abc"}, + {" abc ", "abc"}, + {" abc", "abc"}, + {" aBc de f g\n", "abc de f g"}, +} + +func TestCanonicalizeRDNString(t *testing.T) { + for i, test := range stringCanonTests { + ret := canonicalizeRDNString(test.in) + if test.out != ret { + t.Errorf("#%d: canonicalizeRDNString(%q) = %q, want %q", i, test.in, ret, test.out) + } + } +}