Skip to content

Commit 7329d7d

Browse files
authored
Conform SID string format to MS-DTYP 2.4.2.1 (hex authority + no spurious RID) (Fixes #104) (#105)
1 parent 1a268a7 commit 7329d7d

2 files changed

Lines changed: 111 additions & 4 deletions

File tree

sid/SecurityIdentifier.go

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,16 @@ func (sid *SID) FromString(sidString string) error {
404404
// Parse the identifier authority (S-<Revision>-<IdentifierAuthority>).
405405
// The binary SID format stores this in 6 bytes (see MS-DTYP 2.4.2.1
406406
// SID_IDENTIFIER_AUTHORITY), so reject values that cannot be represented
407-
// rather than silently truncating them at marshal time.
408-
identifierAuthority, err := strconv.ParseUint(parts[2], 10, 64)
407+
// rather than silently truncating them at marshal time. Per MS-DTYP 2.4.2.1
408+
// an identifier authority >= 2^32 is written in hexadecimal ("0x" followed
409+
// by hex digits); smaller values are decimal.
410+
authorityToken := parts[2]
411+
var identifierAuthority uint64
412+
if len(authorityToken) > 2 && (authorityToken[0:2] == "0x" || authorityToken[0:2] == "0X") {
413+
identifierAuthority, err = strconv.ParseUint(authorityToken[2:], 16, 64)
414+
} else {
415+
identifierAuthority, err = strconv.ParseUint(authorityToken, 10, 64)
416+
}
409417
if err != nil {
410418
return fmt.Errorf("invalid identifier authority in SID: %v", err)
411419
}
@@ -451,13 +459,32 @@ func (sid *SID) FromString(sidString string) error {
451459
// This includes the revision level, identifier authority, all sub-authorities, and the
452460
// relative identifier (RID).
453461
func (sid *SID) ToString() string {
454-
sidstring := fmt.Sprintf("S-%d-%d", sid.RevisionLevel, sid.IdentifierAuthority.Value)
462+
// Per MS-DTYP 2.4.2.1, an identifier authority value less than 2^32 is
463+
// written in decimal, while a value >= 2^32 is written in hexadecimal as
464+
// "0x" followed by 12 hex digits.
465+
var authorityStr string
466+
if sid.IdentifierAuthority.Value >= (uint64(1) << 32) {
467+
authorityStr = fmt.Sprintf("0x%012x", sid.IdentifierAuthority.Value)
468+
} else {
469+
authorityStr = fmt.Sprintf("%d", sid.IdentifierAuthority.Value)
470+
}
471+
sidstring := fmt.Sprintf("S-%d-%s", sid.RevisionLevel, authorityStr)
455472

456473
for k := range sid.SubAuthorities {
457474
sidstring += fmt.Sprintf("-%d", sid.SubAuthorities[k])
458475
}
459476

460-
sidstring += fmt.Sprintf("-%d", sid.RelativeIdentifier)
477+
// Append the RID only when the SID actually carries a final sub-authority.
478+
// A SID with no sub-authorities at all — SubAuthorityCount == 0, no entries
479+
// in SubAuthorities, and a zero RID, e.g. the bare NT Authority SID S-1-5
480+
// produced by Unmarshal — has no RID; appending RelativeIdentifier
481+
// unconditionally fabricated a spurious trailing "-0", producing the string
482+
// of a different SID. Any evidence of a RID (a non-zero count, prior
483+
// sub-authorities, or a non-zero RID value) means one is present and must be
484+
// emitted.
485+
if sid.SubAuthorityCount > 0 || len(sid.SubAuthorities) > 0 || sid.RelativeIdentifier != 0 {
486+
sidstring += fmt.Sprintf("-%d", sid.RelativeIdentifier)
487+
}
461488

462489
return sidstring
463490
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package sid_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/TheManticoreProject/winacl/sid"
7+
)
8+
9+
// TestSID_ToString_NoSpuriousRID is a regression test for ToString fabricating a
10+
// trailing "-0" for a SID with SubAuthorityCount == 0. A bare NT Authority SID
11+
// (binary: revision 1, 0 sub-authorities, identifier authority 5) must render
12+
// as "S-1-5", not "S-1-5-0" (which is the canonical string of a different SID).
13+
func TestSID_ToString_NoSpuriousRID(t *testing.T) {
14+
// 01 00 000000000005 : Revision 1, SubAuthorityCount 0, IdentifierAuthority 5.
15+
bare := []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05}
16+
17+
s := &sid.SID{}
18+
if _, err := s.Unmarshal(bare); err != nil {
19+
t.Fatalf("Unmarshal() error = %v", err)
20+
}
21+
if s.SubAuthorityCount != 0 {
22+
t.Fatalf("SubAuthorityCount = %d, want 0", s.SubAuthorityCount)
23+
}
24+
if got := s.ToString(); got != "S-1-5" {
25+
t.Fatalf("ToString() = %q, want %q", got, "S-1-5")
26+
}
27+
28+
// A normal SID must still include its RID.
29+
normal := &sid.SID{}
30+
if err := normal.FromString("S-1-5-32-544"); err != nil {
31+
t.Fatalf("FromString() error = %v", err)
32+
}
33+
if got := normal.ToString(); got != "S-1-5-32-544" {
34+
t.Fatalf("ToString() = %q, want %q", got, "S-1-5-32-544")
35+
}
36+
}
37+
38+
// TestSID_HexIdentifierAuthority is a regression test for the identifier
39+
// authority >= 2^32 not being formatted/parsed in the hexadecimal form that
40+
// MS-DTYP 2.4.2.1 mandates ("0x" followed by 12 hex digits).
41+
func TestSID_HexIdentifierAuthority(t *testing.T) {
42+
// 2^40 = 0x10000000000, which is >= 2^32 and within the 48-bit field.
43+
const authority = uint64(1) << 40
44+
const wantStr = "S-1-0x010000000000-1-2"
45+
46+
// ToString must emit the hex form.
47+
s := &sid.SID{}
48+
if err := s.FromString("S-1-1099511627776-1-2"); err != nil {
49+
t.Fatalf("FromString(decimal) error = %v", err)
50+
}
51+
if s.IdentifierAuthority.Value != authority {
52+
t.Fatalf("parsed authority = %d, want %d", s.IdentifierAuthority.Value, authority)
53+
}
54+
if got := s.ToString(); got != wantStr {
55+
t.Fatalf("ToString() = %q, want %q", got, wantStr)
56+
}
57+
58+
// FromString must accept the hex form and decode the same value.
59+
h := &sid.SID{}
60+
if err := h.FromString(wantStr); err != nil {
61+
t.Fatalf("FromString(hex) error = %v", err)
62+
}
63+
if h.IdentifierAuthority.Value != authority {
64+
t.Fatalf("hex-parsed authority = %d, want %d", h.IdentifierAuthority.Value, authority)
65+
}
66+
67+
// Round-trip: the hex string re-serializes and re-parses to the same value.
68+
if got := h.ToString(); got != wantStr {
69+
t.Fatalf("hex round-trip ToString() = %q, want %q", got, wantStr)
70+
}
71+
72+
// A value < 2^32 must still be decimal.
73+
dec := &sid.SID{}
74+
if err := dec.FromString("S-1-5-18"); err != nil {
75+
t.Fatalf("FromString() error = %v", err)
76+
}
77+
if got := dec.ToString(); got != "S-1-5-18" {
78+
t.Fatalf("ToString() = %q, want %q (small authority must stay decimal)", got, "S-1-5-18")
79+
}
80+
}

0 commit comments

Comments
 (0)