1 module sspi; 2 3 /+ 4 Helper classes for SSPI authentication via the win32security module. 5 SSPI authentication involves a token-exchange "dance", the exact details 6 of which depends on the authentication provider used. There are also 7 a number of complex flags and constants that need to be used - in most 8 cases, there are reasonable defaults. 9 These classes attempt to hide these details from you until you really need 10 to know. They are not designed to handle all cases, just the common ones. 11 If you need finer control than offered here, just use the win32security 12 functions directly. 13 +/ 14 15 /// Based on Roger Upole's sspi demos. 16 version(Windows): 17 import core.sys.windows.ntsecpkg; 18 import core.sys.windows.sspi; 19 import core.sys.windows.windef:DWORD; 20 import sspi.defines; 21 import sspi.helpers; 22 import std.datetime:DateTime; 23 import std.string:toStringz,fromStringz; 24 import std.conv:to; 25 import std.typecons:tuple; 26 import std.utf:toUTF16z; 27 import std.exception; 28 29 enum cbMaxMessage = 12_000; // from SSPI MS example 30 31 struct BaseAuth 32 { 33 SecHandle context; 34 CredHandle credentials; 35 uint nextSequenceNumber; 36 bool isAuthenticated; 37 38 /// Reset everything to an unauthorized state 39 void reset() 40 { 41 this.context = SecHandle.init; 42 this.isAuthenticated = false; 43 this.nextSequenceNumber = 0; 44 } 45 46 /// Get the next sequence number for a transmission. Default implementation is to increment a counter 47 auto getNextSequenceNumber() 48 { 49 auto ret = nextSequenceNumber; 50 nextSequenceNumber++; 51 return ret; 52 } 53 54 auto encrypt(string data) 55 { 56 // auto packageInfo = context.queryContextAttributes!SecPkgNegInfo(SecPackageAttribute.info); 57 auto packageSizes= queryContextAttributes!SecPkgContext_Sizes(&context,SecPackageAttribute.sizes); 58 auto maxSignatureSize = packageSizes.cbMaxSignature; 59 auto trailerSize = packageSizes.cbSecurityTrailer; 60 61 SecBuffer[2] buffers; 62 SecBufferDesc bufferDesc; 63 64 bufferDesc.ulVersion = SECBUFFER_VERSION; 65 bufferDesc.cBuffers = 2; 66 bufferDesc.pBuffers = buffers.ptr; 67 68 // initial DWORD specifies size of trailer block 69 ubyte[] outputData; 70 outputData.length = trailerSize + data.length + DWORD.sizeof; 71 buffers[0].cbBuffer = trailerSize; 72 buffers[0].BufferType = SECBUFFER_TOKEN; 73 buffers[0].pvBuffer = cast(void*)data.ptr + cast(ptrdiff_t) DWORD.sizeof; 74 75 buffers[1].cbBuffer = data.length.to!uint; 76 buffers[1].BufferType = SECBUFFER_DATA; 77 buffers[1].pvBuffer = cast(void*)data.ptr; 78 79 context.encryptMessage(0, bufferDesc, getNextSequenceNumber()); 80 return tuple(buffers[0],buffers[1]); 81 } 82 83 84 /// Decrypt a previously encrypted string, returning the orignal data 85 auto decrypt(string data, string trailer) 86 { 87 SecBuffer[2] buffers; 88 SecBufferDesc bufferDesc; 89 90 bufferDesc.ulVersion = SECBUFFER_VERSION; 91 bufferDesc.cBuffers = 2; 92 bufferDesc.pBuffers = buffers.ptr; 93 94 buffers[0].cbBuffer = trailer.length.to!uint + 1; 95 buffers[0].BufferType = SECBUFFER_TOKEN; 96 buffers[0].pvBuffer = cast(void*)trailer.toStringz; 97 98 buffers[1].cbBuffer = data.length.to!uint +1; // FIXME - might be null terminated already 99 buffers[1].BufferType = SECBUFFER_DATA; 100 buffers[1].pvBuffer = cast(void*)data.toStringz; 101 102 auto fQOP = context.decryptMessage(bufferDesc, getNextSequenceNumber()); 103 return (cast(char*)buffers[0].pvBuffer).fromStringz; 104 } 105 106 /// sign a string suitable for transmission, returning the signature. 107 /// Passing the data and signature to verify will determine if the data is unchanged. 108 string sign(string data) 109 { 110 auto packageSizes = queryContextAttributes!SecPkgContext_Sizes(&context,SecPackageAttribute.sizes); 111 auto trailerSize = packageSizes.cbMaxSignature; 112 113 SecBuffer[2] buffers; 114 SecBufferDesc bufferDesc; 115 116 bufferDesc.ulVersion = SECBUFFER_VERSION; 117 bufferDesc.cBuffers = 2; 118 bufferDesc.pBuffers = buffers.ptr; 119 120 buffers[0].cbBuffer = data.length.to!uint +1; // FIXME - might be null terminated already 121 buffers[0].BufferType = SECBUFFER_DATA; 122 buffers[0].pvBuffer = cast(void*) data.toStringz; 123 124 buffers[1].cbBuffer = trailerSize; 125 buffers[1].BufferType = SECBUFFER_TOKEN; 126 context.makeSignature(0,bufferDesc,this.getNextSequenceNumber()); 127 return (cast(char*)buffers[1].cbBuffer).fromStringz.idup; 128 } 129 130 /// Verifies data and its signature. If verification fails, an sspi.error will be raised. 131 void verifySignature(string data, string sig) 132 { 133 SecBuffer[2] buffers; 134 SecBufferDesc bufferDesc; 135 bufferDesc.ulVersion = SECBUFFER_VERSION; 136 bufferDesc.cBuffers = 2; 137 bufferDesc.pBuffers = buffers.ptr; 138 139 buffers[0].cbBuffer = data.length.to!uint +1; // FIXME - might be null terminated already 140 buffers[0].BufferType = SECBUFFER_DATA; 141 buffers[0].pvBuffer = cast(void*) data.toStringz; 142 143 buffers[1].cbBuffer = sig.length.to!uint +1; // FIXME 144 buffers[1].BufferType = SECBUFFER_TOKEN; 145 buffers[1].pvBuffer = cast(void*) sig.toStringz; 146 147 context.verifySignature(bufferDesc, this.getNextSequenceNumber()); 148 } 149 } 150 151 152 153 154 155 struct ClientAuth 156 { 157 BaseAuth base; 158 alias base this; 159 160 enum DefaultSecurityContextFlags = IscReq.integrity | IscReq.sequenceDetect | IscReq.replayDetect | IscReq.confidentiality; 161 162 IscReq securityContextFlags; 163 long dataRep; 164 string targetSecurityContextProvider; 165 SecPkgInfoW* packageInfo; 166 TimeStamp credentialsExpiry; 167 string packageName; 168 uint contextAttr; 169 170 this(string packageName, string clientName, 171 string targetSecurityContextProvider = null, 172 IscReq securityContextFlags = DefaultSecurityContextFlags, long dataRep = SECURITY_NETWORK_DREP) 173 { 174 import std.stdio; 175 import std.string:fromStringz; 176 177 this.securityContextFlags = securityContextFlags; 178 this.dataRep = dataRep; 179 this.targetSecurityContextProvider = targetSecurityContextProvider; 180 this.packageInfo = querySecurityPackageInfo(packageName); 181 auto result = acquireCredentialsHandle(clientName,packageName); // clientName,packageInfo.Name, SECPKG_CRED_OUTBOUND, 182 this.credentialsExpiry = result[0]; 183 this.base.credentials = result[1]; 184 this.packageName = packageName; 185 } 186 187 188 auto acquireCredentialsHandle(string userName, string packageName) 189 { 190 TimeStamp lifetime; 191 SecurityStatus securityStatus = cast(SecurityStatus) AcquireCredentialsHandleW( 192 cast(wchar*) userName.toUTF16z, 193 cast(wchar*) packageName.toUTF16z, 194 SECPKG_CRED_OUTBOUND, 195 null, 196 null, 197 null, 198 null, 199 &this.base.credentials, 200 &lifetime); 201 enforce(securityStatus.secSuccess, securityStatus.to!string); 202 this.credentialsExpiry = lifetime; 203 return tuple(lifetime, base.credentials); 204 } 205 /// Perform *one* step of the server authentication process. 206 auto authorize(ubyte[] data=[]) 207 { 208 import std.stdio; 209 import std.conv:to; 210 SecurityContextResult result; 211 bool isFirstStage = (data.length == 0); 212 ubyte[] retBuf; 213 retBuf.length = cbMaxMessage; 214 SecBuffer[1] buffersIn, buffersOut; 215 SecBufferDesc bufferDescIn, bufferDescOut; 216 DWORD cbOut = isFirstStage?retBuf.length.to!int : 0; 217 218 bufferDescOut.ulVersion = SECBUFFER_VERSION; 219 bufferDescOut.cBuffers = 1; 220 bufferDescOut.pBuffers = buffersOut.ptr; 221 222 buffersOut[0].cbBuffer = cbOut; 223 buffersOut[0].BufferType = SECBUFFER_TOKEN; 224 buffersOut[0].pvBuffer = retBuf.ptr; 225 226 if(!isFirstStage) 227 { 228 bufferDescIn.ulVersion = SECBUFFER_VERSION; 229 bufferDescIn.cBuffers = 1; 230 bufferDescIn.pBuffers = buffersIn.ptr; 231 232 buffersIn[0].cbBuffer = data.length.to!int; 233 buffersIn[0].BufferType = SECBUFFER_TOKEN; 234 buffersIn[0].pvBuffer = cast(void*) data.ptr; 235 236 buffersOut[0].pvBuffer = null; 237 buffersOut[0].cbBuffer = 0; 238 result = initializeSecurityContext(credentials, &context, packageName, cast(uint)securityContextFlags | ISC_REQ_ALLOCATE_MEMORY, 0U, cast(uint)dataRep,bufferDescIn,bufferDescOut); 239 bufferDescOut=result.outputBufferDesc; 240 } 241 else 242 { 243 buffersOut[0].pvBuffer = null; 244 buffersOut[0].cbBuffer = 0; 245 result = initializeSecurityContextInitial(credentials, &context, this.targetSecurityContextProvider, cast(uint)securityContextFlags | ISC_REQ_ALLOCATE_MEMORY, 0UL, cast(uint)dataRep,bufferDescOut); 246 bufferDescOut=result.outputBufferDesc; 247 //auto result2 = queryContextAttributes!SecPkgInfoW(&context,SecPackageAttribute.negotiationInfo); 248 } 249 250 scope(exit) 251 { 252 if (result.outputBufferDesc.pBuffers !is null) 253 FreeContextBuffer(cast(void*)result.outputBufferDesc.pBuffers); 254 } 255 this.contextAttr = result.contextAttribute; 256 this.credentialsExpiry = result.expiry; 257 auto securityStatus = result.securityStatus; 258 259 //auto result2 = context.queryContextAttributes!SecPkgInfoW(SecPackageAttribute.negotiationInfo); 260 if (securityStatus == SecurityStatus.completeNeeded || securityStatus == SecurityStatus.completeAndContinue) 261 { 262 version(Trace) writefln("securityStatus: %s, completeneeded %s completecontinue%s",securityStatus,cast(long)SecurityStatus.completeNeeded,cast(long)SecurityStatus.completeAndContinue); 263 completeAuthToken(&context,bufferDescOut); 264 } 265 this.isAuthenticated = (securityStatus ==0); 266 auto returnBuffersOut = cast(SecBuffer*)(bufferDescOut.pBuffers); 267 version(Trace) 268 { 269 stderr.writefln("authenticated = %s; %s byte token",securityStatus,returnBuffersOut.cbBuffer); 270 stderr.writeln(this.isAuthenticated,securityStatus,this.credentialsExpiry); 271 } 272 return tuple(securityStatus, ((cast(ubyte*)(returnBuffersOut.pvBuffer)))[0 .. returnBuffersOut.cbBuffer].idup); 273 274 } 275 } 276 277 version(None): 278 /// Manages the server side of an SSPI authentication handshake 279 struct ServerAuth 280 { 281 BaseAuth base; 282 alias base this; 283 string packageName; 284 ulong datarap; 285 IscReq securityContextFlags; 286 void* packageInfo; 287 DateTime credentialsExpiry; 288 bool isAuthenticated; 289 290 this(string packageName, securityContextFlags = 0UL, ulong datarep = SECURITY_NETWORK_DREP) 291 { 292 this.packageName = packageName; 293 this.datarep = datarep; 294 295 this.securityContextFlags = (securityContextFlags==0) ? 296 (ASC_REQ_INTEGRITY|ASC_REQ_SEQUENCE_DETECT | ASC_REQ_REPLAY_DETECT|ASC_REQ_CONFIDENTIALITY) : 297 securityContextFlags; 298 base = BaseAuth(); 299 } 300 301 void setup() 302 { 303 304 packageInfo = QuerySecurityPackageInfo(packageName); 305 this.credentialsExpiry = AcquireCredentialsHandle(packageName, this.packageInfo.Name, SECPKG_CRED_INBOUND, null, null); 306 } 307 308 /// Perform *one* step of the server authentication process. 309 auto authorize(string data = null) 310 { 311 import std.stdio; 312 SecurityStatus result; 313 bool isFirstStage = (data.length == 0); 314 ubyte[] retBuf; 315 retBuf.length = isFirstStage ? 0 : cbMaxMessage; // packageInfo.cbMaxMessage; 316 SecBuffer[1] buffersIn, buffersOut; 317 SecBufferDesc bufferDescIn, bufferDescOut; 318 DWORD cbOut = 0; 319 320 bufferDescOut.ulVersion = SECBUFFER_VERSION; 321 bufferDescOut.cBuffers = 1; 322 bufferDescOut.pBuffers = buffersOut.ptr; 323 324 buffersOut[0].cbBuffer = cbOut; 325 buffersOut[0].BufferType = SECBUFFER_TOKEN; 326 buffersOut[0].pvBuffer = retBuf.ptr; 327 328 bufferDescOut.cbBuffer = this.packageInfo.cbMaxToken; 329 bufferDescOut.BufferType = SECBUFFER_TOKEN; 330 331 if(!isFirstStage) 332 { 333 bufferDescIn.ulVersion = SECBUFFER_VERSION; 334 bufferDescIn.cBuffers = 1; 335 bufferDescIn.pBuffers = buffersIn.ptr; 336 337 buffersIn[0].cbBuffer = this.packageInfo.cbMaxToken; 338 buffersIn[0].BufferType = SECBUFFER_TOKEN; 339 buffersIn[0].pvBuffer = data.toStringz; 340 result = initializeSecurityContext( credentials, context, buffersIn, securityContextFlags, dataRep, bufferDescOut); 341 } 342 else 343 { 344 //result = initializeSecurityContextInitial( credentials, null, securityContextFlags, dataRep, bufferDescOut); 345 } 346 347 this.contextAttr = result[0]; 348 this.contextExpiry = result[1]; 349 auto securityStatus = result[2]; 350 this.context = result[3]; 351 352 if (securityStatus & SEC_I_COMPLETE_NEEDED || securityStatus & SEC_I_COMPLETE_AND_CONTINUE) 353 context.completeAuthToken(bufferDescOut); 354 this.isAuthenticated = (securityStatus ==0); 355 return tuple(securityStatus, buffers[0]); 356 357 } 358 }