1212// See the License for the specific language governing permissions and
1313// limitations under the License.
1414
15+ using NtApiDotNet . Utilities . ASN1 ;
16+ using NtApiDotNet . Win32 . Security . Authentication . Kerberos ;
17+ using NtApiDotNet . Win32 . Security . Authentication . Ntlm ;
18+ using System ;
19+ using System . Collections ;
20+ using System . Collections . Generic ;
1521using System . IO ;
22+ using System . Text ;
1623
1724namespace NtApiDotNet . Win32 . Security . Authentication . Negotiate
1825{
1926 /// <summary>
2027 /// SPNEGO Authentication Token.
2128 /// </summary>
22- public class NegotiateAuthenticationToken : ASN1AuthenticationToken
29+ public abstract class NegotiateAuthenticationToken : ASN1AuthenticationToken
2330 {
24- internal NegotiateAuthenticationToken ( byte [ ] data )
31+ /// <summary>
32+ /// The negotiated authentication token.
33+ /// </summary>
34+ public AuthenticationToken Token { get ; private set ; }
35+
36+ /// <summary>
37+ /// Optional message integrity code.
38+ /// </summary>
39+ public byte [ ] MessageIntegrityCode { get ; }
40+
41+ /// <summary>
42+ /// Decrypt the Authentication Token using a keyset.
43+ /// </summary>
44+ /// <param name="keyset">The set of keys to decrypt the </param>
45+ /// <returns>The decrypted token, or the same token if nothing could be decrypted.</returns>
46+ public override AuthenticationToken Decrypt ( IEnumerable < AuthenticationKey > keyset )
47+ {
48+ if ( Token == null )
49+ return this ;
50+ var ret = ( NegotiateAuthenticationToken ) MemberwiseClone ( ) ;
51+ ret . Token = Token . Decrypt ( keyset ) ;
52+ return ret ;
53+ }
54+
55+ /// <summary>
56+ /// Format the authentication token.
57+ /// </summary>
58+ /// <returns>The token as a formatted string.</returns>
59+ public override string Format ( )
60+ {
61+ StringBuilder builder = new StringBuilder ( ) ;
62+ builder . AppendLine ( $ "<SPNEGO { ( this is NegotiateInitAuthenticationToken ? "Init" : "Response" ) } >") ;
63+ FormatData ( builder ) ;
64+ string token_format = Token ? . Format ( ) ;
65+ if ( ! string . IsNullOrWhiteSpace ( token_format ) )
66+ {
67+ builder . AppendLine ( "<SPNEGO Token>" ) ;
68+ builder . AppendLine ( token_format . TrimEnd ( ) ) ;
69+ builder . AppendLine ( "</SPNEGO Token>" ) ;
70+ }
71+ return builder . ToString ( ) ;
72+ }
73+
74+ private protected abstract void FormatData ( StringBuilder builder ) ;
75+
76+ private static AuthenticationToken ParseToken ( byte [ ] data , int token_count , bool client )
77+ {
78+ if ( NtlmAuthenticationToken . TryParse ( data , token_count , client , out NtlmAuthenticationToken ntlm_token ) )
79+ {
80+ return ntlm_token ;
81+ }
82+
83+ if ( KerberosAuthenticationToken . TryParse ( data , token_count , client , out KerberosAuthenticationToken kerb_token ) )
84+ {
85+ return kerb_token ;
86+ }
87+
88+ return new AuthenticationToken ( data ) ;
89+ }
90+
91+ private static IEnumerable < string > ParseMechList ( DERValue [ ] values )
92+ {
93+ List < string > mech_list = new List < string > ( ) ;
94+ if ( values . CheckValueSequence ( ) )
95+ {
96+ foreach ( var next in values [ 0 ] . Children )
97+ {
98+ if ( ! next . CheckPrimitive ( UniversalTag . OBJECT_IDENTIFIER ) )
99+ {
100+ throw new InvalidDataException ( ) ;
101+ }
102+ mech_list . Add ( next . ReadObjID ( ) ) ;
103+ }
104+ }
105+ return mech_list . AsReadOnly ( ) ;
106+ }
107+
108+ private static NegotiateContextFlags ConvertContextFlags ( BitArray flags )
109+ {
110+ if ( flags . Length > 32 )
111+ throw new InvalidDataException ( ) ;
112+ int ret = 0 ;
113+ for ( int i = 0 ; i < flags . Length ; ++ i )
114+ {
115+ if ( flags [ i ] )
116+ ret |= ( 1 << i ) ;
117+ }
118+ return ( NegotiateContextFlags ) ret ;
119+ }
120+
121+ private static bool ParseInit ( byte [ ] data , DERValue [ ] values , int token_count , bool client , out NegotiateAuthenticationToken token )
122+ {
123+ token = null ;
124+ if ( ! values . CheckValueSequence ( ) )
125+ {
126+ return false ;
127+ }
128+
129+ IEnumerable < string > mech_list = null ;
130+ NegotiateContextFlags flags = NegotiateContextFlags . None ;
131+ AuthenticationToken auth_token = null ;
132+ byte [ ] mic = null ;
133+
134+ foreach ( var next in values [ 0 ] . Children )
135+ {
136+ if ( next . Type != DERTagType . ContextSpecific )
137+ return false ;
138+ switch ( next . Tag )
139+ {
140+ case 0 :
141+ mech_list = ParseMechList ( next . Children ) ;
142+ break ;
143+ case 1 :
144+ flags = ConvertContextFlags ( next . ReadChildBitString ( ) ) ;
145+ break ;
146+ case 2 :
147+ auth_token = ParseToken ( next . ReadChildOctetString ( ) , token_count , client ) ;
148+ break ;
149+ case 3 :
150+ // If NegTokenInit2 then just ignore neg hints.
151+ if ( next . HasChildren ( ) && next . Children [ 0 ] . CheckSequence ( ) )
152+ break ;
153+ mic = next . ReadChildOctetString ( ) ;
154+ break ;
155+ case 4 :
156+ // Used if NegTokenInit2.
157+ mic = next . ReadChildOctetString ( ) ;
158+ break ;
159+ default :
160+ return false ;
161+ }
162+ }
163+
164+ token = new NegotiateInitAuthenticationToken ( data , mech_list , flags , auth_token , mic ) ;
165+ return true ;
166+ }
167+
168+ private static bool ParseResp ( byte [ ] data , DERValue [ ] values , int token_count , bool client , out NegotiateAuthenticationToken token )
169+ {
170+ token = null ;
171+ if ( ! values . CheckValueSequence ( ) )
172+ {
173+ return false ;
174+ }
175+
176+ string mech = null ;
177+ NegotiateAuthenticationState state = NegotiateAuthenticationState . Reject ;
178+ AuthenticationToken auth_token = null ;
179+ byte [ ] mic = null ;
180+
181+ foreach ( var next in values [ 0 ] . Children )
182+ {
183+ if ( next . Type != DERTagType . ContextSpecific )
184+ return false ;
185+ switch ( next . Tag )
186+ {
187+ case 0 :
188+ state = ( NegotiateAuthenticationState ) next . ReadChildEnumerated ( ) ;
189+ break ;
190+ case 1 :
191+ mech = next . ReadChildObjID ( ) ;
192+ break ;
193+ case 2 :
194+ auth_token = ParseToken ( next . ReadChildOctetString ( ) , token_count , client ) ;
195+ break ;
196+ case 3 :
197+ mic = next . ReadChildOctetString ( ) ;
198+ break ;
199+ default :
200+ return false ;
201+ }
202+ }
203+
204+ token = new NegotiateResponseAuthenticationToken ( data , mech , state , auth_token , mic ) ;
205+ return true ;
206+ }
207+
208+ private protected NegotiateAuthenticationToken ( byte [ ] data , AuthenticationToken token , byte [ ] mic )
25209 : base ( data )
26210 {
211+ Token = token ;
212+ MessageIntegrityCode = mic ;
213+ }
214+
215+ #region Public Static Methods
216+ /// <summary>
217+ /// Parse bytes into a negotiate token.
218+ /// </summary>
219+ /// <param name="data">The negotiate token in bytes.</param>
220+ /// <returns>The Negotiate token.</returns>
221+ public static NegotiateAuthenticationToken Parse ( byte [ ] data )
222+ {
223+ if ( ! TryParse ( data , 0 , false , out NegotiateAuthenticationToken token ) )
224+ {
225+ throw new ArgumentException ( nameof ( data ) ) ;
226+ }
227+ return token ;
27228 }
229+ #endregion
28230
29231 #region Internal Static Methods
30232 /// <summary>
@@ -40,13 +242,45 @@ internal static bool TryParse(byte[] data, int token_count, bool client, out Neg
40242 token = null ;
41243 try
42244 {
43- token = new NegotiateAuthenticationToken ( data ) ;
44- return true ;
245+ byte [ ] token_data ;
246+ if ( GSSAPIUtils . TryParse ( data , out token_data , out string oid ) )
247+ {
248+ if ( oid != OIDValues . SPNEGO )
249+ {
250+ return false ;
251+ }
252+ }
253+ else
254+ {
255+ token_data = data ;
256+ }
257+
258+ DERValue [ ] values = DERParser . ParseData ( token_data , 0 ) ;
259+ if ( values . Length != 1 || values [ 0 ] . Type != DERTagType . ContextSpecific )
260+ {
261+ return false ;
262+ }
263+
264+ if ( values [ 0 ] . CheckContext ( 0 ) )
265+ {
266+ return ParseInit ( data , values [ 0 ] . Children , token_count , client , out token ) ;
267+ }
268+ else if ( values [ 0 ] . CheckContext ( 1 ) )
269+ {
270+ return ParseResp ( data , values [ 0 ] . Children , token_count , client , out token ) ;
271+ }
272+ else
273+ {
274+ return false ;
275+ }
45276 }
46277 catch ( EndOfStreamException )
47278 {
48- return false ;
49279 }
280+ catch ( InvalidDataException )
281+ {
282+ }
283+ return false ;
50284 }
51285 #endregion
52286 }
0 commit comments