1 /* -*- Mode: C; tab-width: 4 -*-
2 *
3 * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 #include "Secret.h"
19 #include <stdarg.h>
20 #include <stddef.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <winsock2.h>
25 #include <ws2tcpip.h>
26 #include <windows.h>
27 #include <process.h>
28 #include <ntsecapi.h>
29 #include <lm.h>
30 #include "DebugServices.h"
31
32
33 mDNSlocal OSStatus MakeLsaStringFromUTF8String( PLSA_UNICODE_STRING output, const char * input );
34 mDNSlocal OSStatus MakeUTF8StringFromLsaString( char * output, size_t len, PLSA_UNICODE_STRING input );
35
36
37 BOOL
LsaGetSecret(const char * inDomain,char * outDomain,unsigned outDomainSize,char * outKey,unsigned outKeySize,char * outSecret,unsigned outSecretSize)38 LsaGetSecret( const char * inDomain, char * outDomain, unsigned outDomainSize, char * outKey, unsigned outKeySize, char * outSecret, unsigned outSecretSize )
39 {
40 PLSA_UNICODE_STRING domainLSA;
41 PLSA_UNICODE_STRING keyLSA;
42 PLSA_UNICODE_STRING secretLSA;
43 size_t i;
44 size_t dlen;
45 LSA_OBJECT_ATTRIBUTES attrs;
46 LSA_HANDLE handle = NULL;
47 NTSTATUS res;
48 OSStatus err;
49
50 check( inDomain );
51 check( outDomain );
52 check( outKey );
53 check( outSecret );
54
55 // Initialize
56
57 domainLSA = NULL;
58 keyLSA = NULL;
59 secretLSA = NULL;
60
61 // Make sure we have enough space to add trailing dot
62
63 dlen = strlen( inDomain );
64 err = strcpy_s( outDomain, outDomainSize - 2, inDomain );
65 require_noerr( err, exit );
66
67 // If there isn't a trailing dot, add one because the mDNSResponder
68 // presents names with the trailing dot.
69
70 if ( outDomain[ dlen - 1 ] != '.' )
71 {
72 outDomain[ dlen++ ] = '.';
73 outDomain[ dlen ] = '\0';
74 }
75
76 // Canonicalize name by converting to lower case (keychain and some name servers are case sensitive)
77
78 for ( i = 0; i < dlen; i++ )
79 {
80 outDomain[i] = (char) tolower( outDomain[i] ); // canonicalize -> lower case
81 }
82
83 // attrs are reserved, so initialize to zeroes.
84
85 ZeroMemory( &attrs, sizeof( attrs ) );
86
87 // Get a handle to the Policy object on the local system
88
89 res = LsaOpenPolicy( NULL, &attrs, POLICY_GET_PRIVATE_INFORMATION, &handle );
90 err = translate_errno( res == 0, LsaNtStatusToWinError( res ), kUnknownErr );
91 require_noerr( err, exit );
92
93 // Get the encrypted data
94
95 domainLSA = ( PLSA_UNICODE_STRING ) malloc( sizeof( LSA_UNICODE_STRING ) );
96 require_action( domainLSA != NULL, exit, err = mStatus_NoMemoryErr );
97 err = MakeLsaStringFromUTF8String( domainLSA, outDomain );
98 require_noerr( err, exit );
99
100 // Retrieve the key
101
102 res = LsaRetrievePrivateData( handle, domainLSA, &keyLSA );
103 err = translate_errno( res == 0, LsaNtStatusToWinError( res ), kUnknownErr );
104 require_noerr_quiet( err, exit );
105
106 // <rdar://problem/4192119> Lsa secrets use a flat naming space. Therefore, we will prepend "$" to the keyname to
107 // make sure it doesn't conflict with a zone name.
108 // Strip off the "$" prefix.
109
110 err = MakeUTF8StringFromLsaString( outKey, outKeySize, keyLSA );
111 require_noerr( err, exit );
112 require_action( outKey[0] == '$', exit, err = kUnknownErr );
113 memcpy( outKey, outKey + 1, strlen( outKey ) );
114
115 // Retrieve the secret
116
117 res = LsaRetrievePrivateData( handle, keyLSA, &secretLSA );
118 err = translate_errno( res == 0, LsaNtStatusToWinError( res ), kUnknownErr );
119 require_noerr_quiet( err, exit );
120
121 // Convert the secret to UTF8 string
122
123 err = MakeUTF8StringFromLsaString( outSecret, outSecretSize, secretLSA );
124 require_noerr( err, exit );
125
126 exit:
127
128 if ( domainLSA != NULL )
129 {
130 if ( domainLSA->Buffer != NULL )
131 {
132 free( domainLSA->Buffer );
133 }
134
135 free( domainLSA );
136 }
137
138 if ( keyLSA != NULL )
139 {
140 LsaFreeMemory( keyLSA );
141 }
142
143 if ( secretLSA != NULL )
144 {
145 LsaFreeMemory( secretLSA );
146 }
147
148 if ( handle )
149 {
150 LsaClose( handle );
151 handle = NULL;
152 }
153
154 return ( !err ) ? TRUE : FALSE;
155 }
156
157
158 mDNSBool
LsaSetSecret(const char * inDomain,const char * inKey,const char * inSecret)159 LsaSetSecret( const char * inDomain, const char * inKey, const char * inSecret )
160 {
161 size_t inDomainLength;
162 size_t inKeyLength;
163 char domain[ 1024 ];
164 char key[ 1024 ];
165 LSA_OBJECT_ATTRIBUTES attrs;
166 LSA_HANDLE handle = NULL;
167 NTSTATUS res;
168 LSA_UNICODE_STRING lucZoneName;
169 LSA_UNICODE_STRING lucKeyName;
170 LSA_UNICODE_STRING lucSecretName;
171 BOOL ok = TRUE;
172 OSStatus err;
173
174 require_action( inDomain != NULL, exit, ok = FALSE );
175 require_action( inKey != NULL, exit, ok = FALSE );
176 require_action( inSecret != NULL, exit, ok = FALSE );
177
178 // If there isn't a trailing dot, add one because the mDNSResponder
179 // presents names with the trailing dot.
180
181 ZeroMemory( domain, sizeof( domain ) );
182 inDomainLength = strlen( inDomain );
183 require_action( inDomainLength > 0, exit, ok = FALSE );
184 err = strcpy_s( domain, sizeof( domain ) - 2, inDomain );
185 require_action( !err, exit, ok = FALSE );
186
187 if ( domain[ inDomainLength - 1 ] != '.' )
188 {
189 domain[ inDomainLength++ ] = '.';
190 domain[ inDomainLength ] = '\0';
191 }
192
193 // <rdar://problem/4192119>
194 //
195 // Prepend "$" to the key name, so that there will
196 // be no conflict between the zone name and the key
197 // name
198
199 ZeroMemory( key, sizeof( key ) );
200 inKeyLength = strlen( inKey );
201 require_action( inKeyLength > 0 , exit, ok = FALSE );
202 key[ 0 ] = '$';
203 err = strcpy_s( key + 1, sizeof( key ) - 3, inKey );
204 require_action( !err, exit, ok = FALSE );
205 inKeyLength++;
206
207 if ( key[ inKeyLength - 1 ] != '.' )
208 {
209 key[ inKeyLength++ ] = '.';
210 key[ inKeyLength ] = '\0';
211 }
212
213 // attrs are reserved, so initialize to zeroes.
214
215 ZeroMemory( &attrs, sizeof( attrs ) );
216
217 // Get a handle to the Policy object on the local system
218
219 res = LsaOpenPolicy( NULL, &attrs, POLICY_ALL_ACCESS, &handle );
220 err = translate_errno( res == 0, LsaNtStatusToWinError( res ), kUnknownErr );
221 require_noerr( err, exit );
222
223 // Intializing PLSA_UNICODE_STRING structures
224
225 err = MakeLsaStringFromUTF8String( &lucZoneName, domain );
226 require_noerr( err, exit );
227
228 err = MakeLsaStringFromUTF8String( &lucKeyName, key );
229 require_noerr( err, exit );
230
231 err = MakeLsaStringFromUTF8String( &lucSecretName, inSecret );
232 require_noerr( err, exit );
233
234 // Store the private data.
235
236 res = LsaStorePrivateData( handle, &lucZoneName, &lucKeyName );
237 err = translate_errno( res == 0, LsaNtStatusToWinError( res ), kUnknownErr );
238 require_noerr( err, exit );
239
240 res = LsaStorePrivateData( handle, &lucKeyName, &lucSecretName );
241 err = translate_errno( res == 0, LsaNtStatusToWinError( res ), kUnknownErr );
242 require_noerr( err, exit );
243
244 exit:
245
246 if ( handle )
247 {
248 LsaClose( handle );
249 handle = NULL;
250 }
251
252 return ok;
253 }
254
255
256 //===========================================================================================================================
257 // MakeLsaStringFromUTF8String
258 //===========================================================================================================================
259
260 mDNSlocal OSStatus
MakeLsaStringFromUTF8String(PLSA_UNICODE_STRING output,const char * input)261 MakeLsaStringFromUTF8String( PLSA_UNICODE_STRING output, const char * input )
262 {
263 int size;
264 OSStatus err;
265
266 check( input );
267 check( output );
268
269 output->Buffer = NULL;
270
271 size = MultiByteToWideChar( CP_UTF8, 0, input, -1, NULL, 0 );
272 err = translate_errno( size > 0, GetLastError(), kUnknownErr );
273 require_noerr( err, exit );
274
275 output->Length = (USHORT)( size * sizeof( wchar_t ) );
276 output->Buffer = (PWCHAR) malloc( output->Length );
277 require_action( output->Buffer, exit, err = mStatus_NoMemoryErr );
278 size = MultiByteToWideChar( CP_UTF8, 0, input, -1, output->Buffer, size );
279 err = translate_errno( size > 0, GetLastError(), kUnknownErr );
280 require_noerr( err, exit );
281
282 // We're going to subtrace one wchar_t from the size, because we didn't
283 // include it when we encoded the string
284
285 output->MaximumLength = output->Length;
286 output->Length -= sizeof( wchar_t );
287
288 exit:
289
290 if ( err && output->Buffer )
291 {
292 free( output->Buffer );
293 output->Buffer = NULL;
294 }
295
296 return( err );
297 }
298
299
300
301 //===========================================================================================================================
302 // MakeUTF8StringFromLsaString
303 //===========================================================================================================================
304
305 mDNSlocal OSStatus
MakeUTF8StringFromLsaString(char * output,size_t len,PLSA_UNICODE_STRING input)306 MakeUTF8StringFromLsaString( char * output, size_t len, PLSA_UNICODE_STRING input )
307 {
308 size_t size;
309 OSStatus err = kNoErr;
310
311 // The Length field of this structure holds the number of bytes,
312 // but WideCharToMultiByte expects the number of wchar_t's. So
313 // we divide by sizeof(wchar_t) to get the correct number.
314
315 size = (size_t) WideCharToMultiByte(CP_UTF8, 0, input->Buffer, ( input->Length / sizeof( wchar_t ) ), NULL, 0, NULL, NULL);
316 err = translate_errno( size != 0, GetLastError(), kUnknownErr );
317 require_noerr( err, exit );
318
319 // Ensure that we have enough space (Add one for trailing '\0')
320
321 require_action( ( size + 1 ) <= len, exit, err = mStatus_NoMemoryErr );
322
323 // Convert the string
324
325 size = (size_t) WideCharToMultiByte( CP_UTF8, 0, input->Buffer, ( input->Length / sizeof( wchar_t ) ), output, (int) size, NULL, NULL);
326 err = translate_errno( size != 0, GetLastError(), kUnknownErr );
327 require_noerr( err, exit );
328
329 // have to add the trailing 0 because WideCharToMultiByte doesn't do it,
330 // although it does return the correct size
331
332 output[size] = '\0';
333
334 exit:
335
336 return err;
337 }
338
339