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