View Javadoc
1   /*
2    * Copyright 2013–2026 Michael Osipov
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.michaelo.tomcat.realm;
17  
18  import java.nio.Buffer;
19  import java.nio.ByteBuffer;
20  import java.nio.ByteOrder;
21  import java.util.Arrays;
22  
23  /**
24   * A class representing a <a href=
25   * "https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/f992ad60-0fe4-4b87-9fed-beb478836861">{@code SID}
26   * (security identifier)</a> from MS-DTYP.
27   */
28  public class Sid {
29  
30  	public static final Sid NULL_SID = new Sid(new byte[] {
31  		(byte) 0x01,
32  		(byte) 0x01,
33  		(byte) 0x00,
34  		(byte) 0x00,
35  		(byte) 0x00,
36  		(byte) 0x00,
37  		(byte) 0x00,
38  		(byte) 0x00,
39  		(byte) 0x00,
40  		(byte) 0x00,
41  		(byte) 0x00,
42  		(byte) 0x00
43  	});
44  
45  	public static final Sid ANONYMOUS_SID = new Sid(new byte[] {
46  		(byte) 0x01,
47  		(byte) 0x01,
48  		(byte) 0x00,
49  		(byte) 0x00,
50  		(byte) 0x00,
51  		(byte) 0x00,
52  		(byte) 0x00,
53  		(byte) 0x05,
54  		(byte) 0x07,
55  		(byte) 0x00,
56  		(byte) 0x00,
57  		(byte) 0x00
58  	});
59  
60  	private byte[] bytes;
61  
62  	private int revision;
63  	private int subAuthorityCount;
64  	private byte[] identifierAuthority;
65  	private long[] subAuthorities;
66  
67  	private String sidString;
68  
69  	/**
70  	 * Parses a SID object from a byte array.
71  	 *
72  	 * @param sidBytes SID structure encoded as bytes
73  	 * @throws NullPointerException if {@code sidBytes} is null
74  	 * @throws IllegalArgumentException if {@code sidBytes} contains less than 12 bytes
75  	 * @throws IllegalArgumentException if SID's revision is not 1
76  	 * @throws IllegalArgumentException if SID's subauthority count is more than 15
77  	 */
78  	public Sid(byte[] sidBytes) {
79  		if (sidBytes == null) throw new NullPointerException("sidBytes cannot be null");
80  		if (sidBytes.length < 12)
81  			throw new IllegalArgumentException("SID must be at least 12 bytes long but is " + sidBytes.length);
82  
83  		ByteBuffer buf = ByteBuffer.wrap(sidBytes);
84  		buf.order(ByteOrder.LITTLE_ENDIAN);
85  
86  		// Always 0x01
87  		this.revision = buf.get() & 0xFF;
88  		if (this.revision != 0x01) throw new IllegalArgumentException("SID revision must be 1 but is " + this.revision);
89  
90  		// At most 15 subauthorities
91  		this.subAuthorityCount = buf.get() & 0xFF;
92  		if (this.subAuthorityCount > 15)
93  			throw new IllegalArgumentException(
94  					"SID subauthority count must be at most 15 but is " + this.subAuthorityCount);
95  
96  		this.identifierAuthority = new byte[6];
97  		buf.get(this.identifierAuthority);
98  
99  		StringBuilder sidStringBuilder = new StringBuilder("S");
100 
101 		sidStringBuilder.append('-').append(this.revision);
102 
103 		ByteBuffer iaBuf = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
104 		((Buffer) iaBuf).position(2);
105 		iaBuf.put(this.identifierAuthority);
106 		((Buffer) iaBuf).flip();
107 
108 		sidStringBuilder.append('-').append(iaBuf.getLong());
109 
110 		this.subAuthorities = new long[this.subAuthorityCount];
111 		for (byte b = 0; b < this.subAuthorityCount; b++) {
112 			this.subAuthorities[b] = buf.getInt() & 0xffffffffL;
113 
114 			sidStringBuilder.append('-').append(this.subAuthorities[b]);
115 		}
116 
117 		this.bytes = Arrays.copyOf(sidBytes, sidBytes.length);
118 		this.sidString = sidStringBuilder.toString();
119 	}
120 
121 	public Sid append(long relativeId) {
122 		byte[] sidBytes = this.bytes;
123 		byte[] appendedSidBytes = new byte[sidBytes.length + 4];
124 		System.arraycopy(sidBytes, 0, appendedSidBytes, 0, sidBytes.length);
125 		appendedSidBytes[1] = (byte) (this.subAuthorityCount + 1);
126 		int signedRelativeId = (int) (relativeId);
127 		appendedSidBytes[sidBytes.length + 0] = (byte) (signedRelativeId & 0xFF);
128 		appendedSidBytes[sidBytes.length + 1] = (byte) ((signedRelativeId >> 8) & 0xFF);
129 		appendedSidBytes[sidBytes.length + 2] = (byte) ((signedRelativeId >> 16) & 0xFF);
130 		appendedSidBytes[sidBytes.length + 3] = (byte) ((signedRelativeId >> 24) & 0xFF);
131 		return new Sid(appendedSidBytes);
132 	}
133 
134 	public byte[] getBytes() {
135 		return Arrays.copyOf(bytes, bytes.length);
136 	}
137 
138 	@Override
139 	public boolean equals(Object obj) {
140 		if (obj == null) return false;
141 
142 		if (!(obj instanceof Sid)) return false;
143 
144 		Sid that = (Sid) obj;
145 
146 		if (this == that) return true;
147 
148 		return Arrays.equals(this.bytes, that.bytes);
149 	}
150 
151 	@Override
152 	public int hashCode() {
153 		return Arrays.hashCode(this.bytes);
154 	}
155 
156 	@Override
157 	public String toString() {
158 		return sidString;
159 	}
160 }