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) FreeContextBuffer(cast(void*)result.outputBufferDesc.pBuffers); 251 this.contextAttr = result.contextAttribute; 252 this.credentialsExpiry = result.expiry; 253 auto securityStatus = result.securityStatus; 254 255 //auto result2 = context.queryContextAttributes!SecPkgInfoW(SecPackageAttribute.negotiationInfo); 256 if (securityStatus == SecurityStatus.completeNeeded || securityStatus == SecurityStatus.completeAndContinue) 257 { 258 version(Trace) writefln("securityStatus: %s, completeneeded %s completecontinue%s",securityStatus,cast(long)SecurityStatus.completeNeeded,cast(long)SecurityStatus.completeAndContinue); 259 completeAuthToken(&context,bufferDescOut); 260 } 261 this.isAuthenticated = (securityStatus ==0); 262 auto returnBuffersOut = cast(SecBuffer*)(bufferDescOut.pBuffers); 263 version(Trace) 264 { 265 stderr.writefln("authenticated = %s; %s byte token",securityStatus,returnBuffersOut.cbBuffer); 266 stderr.writeln(this.isAuthenticated,securityStatus,this.credentialsExpiry); 267 } 268 return tuple(securityStatus, ((cast(ubyte*)(returnBuffersOut.pvBuffer)))[0 .. returnBuffersOut.cbBuffer].idup); 269 270 } 271 } 272 273 version(None): 274 /// Manages the server side of an SSPI authentication handshake 275 struct ServerAuth 276 { 277 BaseAuth base; 278 alias base this; 279 string packageName; 280 ulong datarap; 281 IscReq securityContextFlags; 282 void* packageInfo; 283 DateTime credentialsExpiry; 284 bool isAuthenticated; 285 286 this(string packageName, securityContextFlags = 0UL, ulong datarep = SECURITY_NETWORK_DREP) 287 { 288 this.packageName = packageName; 289 this.datarep = datarep; 290 291 this.securityContextFlags = (securityContextFlags==0) ? 292 (ASC_REQ_INTEGRITY|ASC_REQ_SEQUENCE_DETECT | ASC_REQ_REPLAY_DETECT|ASC_REQ_CONFIDENTIALITY) : 293 securityContextFlags; 294 base = BaseAuth(); 295 } 296 297 void setup() 298 { 299 300 packageInfo = QuerySecurityPackageInfo(packageName); 301 this.credentialsExpiry = AcquireCredentialsHandle(packageName, this.packageInfo.Name, SECPKG_CRED_INBOUND, null, null); 302 } 303 304 /// Perform *one* step of the server authentication process. 305 auto authorize(string data = null) 306 { 307 import std.stdio; 308 SecurityStatus result; 309 bool isFirstStage = (data.length == 0); 310 ubyte[] retBuf; 311 retBuf.length = isFirstStage ? 0 : cbMaxMessage; // packageInfo.cbMaxMessage; 312 SecBuffer[1] buffersIn, buffersOut; 313 SecBufferDesc bufferDescIn, bufferDescOut; 314 DWORD cbOut = 0; 315 316 bufferDescOut.ulVersion = SECBUFFER_VERSION; 317 bufferDescOut.cBuffers = 1; 318 bufferDescOut.pBuffers = buffersOut.ptr; 319 320 buffersOut[0].cbBuffer = cbOut; 321 buffersOut[0].BufferType = SECBUFFER_TOKEN; 322 buffersOut[0].pvBuffer = retBuf.ptr; 323 324 bufferDescOut.cbBuffer = this.packageInfo.cbMaxToken; 325 bufferDescOut.BufferType = SECBUFFER_TOKEN; 326 327 if(!isFirstStage) 328 { 329 bufferDescIn.ulVersion = SECBUFFER_VERSION; 330 bufferDescIn.cBuffers = 1; 331 bufferDescIn.pBuffers = buffersIn.ptr; 332 333 buffersIn[0].cbBuffer = this.packageInfo.cbMaxToken; 334 buffersIn[0].BufferType = SECBUFFER_TOKEN; 335 buffersIn[0].pvBuffer = data.toStringz; 336 result = initializeSecurityContext( credentials, context, buffersIn, securityContextFlags, dataRep, bufferDescOut); 337 } 338 else 339 { 340 //result = initializeSecurityContextInitial( credentials, null, securityContextFlags, dataRep, bufferDescOut); 341 } 342 343 this.contextAttr = result[0]; 344 this.contextExpiry = result[1]; 345 auto securityStatus = result[2]; 346 this.context = result[3]; 347 348 if (securityStatus & SEC_I_COMPLETE_NEEDED || securityStatus & SEC_I_COMPLETE_AND_CONTINUE) 349 context.completeAuthToken(bufferDescOut); 350 this.isAuthenticated = (securityStatus ==0); 351 return tuple(securityStatus, buffers[0]); 352 353 } 354 }