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= context.queryContextAttributes!SecPkgContext_Sizes(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(ptr_diff_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 = context.queryContextAttributes!SecPkgContext_Sizes(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 | 161 IscReq.replayDetect | IscReq.confidentiality; 162 163 IscReq securityContextFlags; 164 long dataRep; 165 string targetSecurityContextProvider; 166 SecPkgInfoW* packageInfo; 167 TimeStamp credentialsExpiry; 168 string packageName; 169 uint contextAttr; 170 171 this(string packageName, string clientName, 172 string targetSecurityContextProvider = null, 173 IscReq securityContextFlags = DefaultSecurityContextFlags, long dataRep = SECURITY_NETWORK_DREP) 174 { 175 this.securityContextFlags = securityContextFlags; 176 this.dataRep = dataRep; 177 this.targetSecurityContextProvider = targetSecurityContextProvider; 178 this.packageInfo = querySecurityPackageInfo(packageName); 179 auto result = acquireCredentialsHandle(packageName); // clientName,packageInfo.Name, SECPKG_CRED_OUTBOUND, 180 this.credentialsExpiry = result[0]; 181 this.base.credentials = result[1]; 182 this.packageName = packageName; 183 } 184 185 186 auto acquireCredentialsHandle(string packageName) 187 { 188 TimeStamp lifetime; 189 SecurityStatus securityStatus = cast(SecurityStatus) AcquireCredentialsHandleW( 190 null, 191 cast(wchar*) packageName.toUTF16z, 192 SECPKG_CRED_OUTBOUND, 193 null, 194 null, 195 null, 196 null, 197 &this.base.credentials, 198 &lifetime); 199 enforce(securityStatus.secSuccess, securityStatus.to!string); 200 this.credentialsExpiry = lifetime; 201 return tuple(lifetime, base.credentials); 202 } 203 /// Perform *one* step of the server authentication process. 204 auto authorize(string data = null) 205 { 206 SecurityContextResult result; 207 bool isFirstStage = (data.length == 0); 208 ubyte[] retBuf; 209 auto packageSizes= context.queryContextAttributes!SecPkgContext_Sizes(SecPackageAttribute.sizes); 210 retBuf.length = isFirstStage ? 0 : packageSizes.cbMaxMessage; 211 SecBuffer[1] buffersIn, buffersOut; 212 SecBufferDesc bufferDescIn, bufferDescOut; 213 DWORD cbOut = 0; 214 215 bufferDescOut.ulVersion = SECBUFFER_VERSION; 216 bufferDescOut.cBuffers = 1; 217 bufferDescOut.pBuffers = buffersOut.ptr; 218 219 buffersOut[0].cbBuffer = cbOut; 220 buffersOut[0].BufferType = SECBUFFER_TOKEN; 221 buffersOut[0].pvBuffer = retBuf.ptr; 222 223 if(!isFirstStage) 224 { 225 bufferDescIn.ulVersion = SECBUFFER_VERSION; 226 bufferDescIn.cBuffers = 1; 227 bufferDescIn.pBuffers = buffersIn.ptr; 228 229 buffersIn[0].cbBuffer = packageSizes.cbMaxToken; 230 buffersIn[0].BufferType = SECBUFFER_TOKEN; 231 buffersIn[0].pvBuffer = cast(void*) data.toStringz; 232 result = initializeSecurityContext(credentials, context, packageName, cast(uint)securityContextFlags, 0U, cast(uint)dataRep,bufferDescIn,bufferDescOut); 233 } 234 else 235 { 236 result = initializeSecurityContext(credentials, context, packageName, cast(uint)securityContextFlags, 0, cast(uint)dataRep,null,bufferDescOut); 237 } 238 239 this.contextAttr = result.contextAttribute; 240 this.credentialsExpiry = result.expiry; 241 auto securityStatus = result.securityStatus; 242 this.context = result.newContext; 243 244 if (securityStatus & SecurityStatus.completeNeeded || securityStatus & SecurityStatus.completeAndContinue) 245 context.completeAuthToken(bufferDescOut); 246 this.isAuthenticated = (securityStatus ==0); 247 return tuple(securityStatus, buffersOut[0]); 248 249 } 250 } 251 252 version(None): 253 /// Manages the server side of an SSPI authentication handshake 254 struct ServerAuth 255 { 256 BaseAuth base; 257 alias base this; 258 string packageName; 259 ulong datarap; 260 IscReq securityContextFlags; 261 void* packageInfo; 262 DateTime credentialsExpiry; 263 bool isAuthenticated; 264 265 this(string packageName, securityContextFlags = 0UL, ulong datarep = SECURITY_NETWORK_DREP) 266 { 267 this.packageName = packageName; 268 this.datarep = datarep; 269 270 this.securityContextFlags = (securityContextFlags==0) ? 271 (ASC_REQ_INTEGRITY|ASC_REQ_SEQUENCE_DETECT | ASC_REQ_REPLAY_DETECT|ASC_REQ_CONFIDENTIALITY) : 272 securityContextFlags; 273 base = BaseAuth(); 274 } 275 276 void setup() 277 { 278 279 packageInfo = QuerySecurityPackageInfo(packageName); 280 this.credentialsExpiry = AcquireCredentialsHandle(packageName, this.packageInfo.Name, SECPKG_CRED_INBOUND, null, null); 281 } 282 283 /// Perform *one* step of the server authentication process. 284 auto authorize(string data = null) 285 { 286 SecurityStatus result; 287 bool isFirstStage = (data.length == 0); 288 ubyte[] retBuf; 289 retBuf.length = isFirstStage ? 0 : cbMaxMessage; // packageInfo.cbMaxMessage; 290 SecBuffer[1] buffersIn, buffersOut; 291 SecBufferDesc bufferDescIn, bufferDescOut; 292 DWORD cbOut = 0; 293 294 bufferDescOut.ulVersion = SECBUFFER_VERSION; 295 bufferDescOut.cBuffers = 1; 296 bufferDescOut.pBuffers = buffersOut.ptr; 297 298 buffersOut[0].cbBuffer = cbOut; 299 buffersOut[0].BufferType = SECBUFFER_TOKEN; 300 buffersOut[0].pvBuffer = retBuf.ptr; 301 302 bufferDescOut.cbBuffer = this.packageInfo.cbMaxToken; 303 bufferDescOut.BufferType = SECBUFFER_TOKEN; 304 305 if(!isFirstStage) 306 { 307 bufferDescIn.ulVersion = SECBUFFER_VERSION; 308 bufferDescIn.cBuffers = 1; 309 bufferDescIn.pBuffers = buffersIn.ptr; 310 311 buffersIn[0].cbBuffer = this.packageInfo.cbMaxToken; 312 buffersIn[0].BufferType = SECBUFFER_TOKEN; 313 buffersIn[0].pvBuffer = data.toStringz; 314 result = initializeSecurityContext( credentials, context, buffersIn, securityContextFlags, dataRep, bufferDescOut); 315 } 316 else 317 { 318 result = initializeSecurityContext( credentials, context, null, securityContextFlags, dataRep, bufferDescOut); 319 } 320 321 this.contextAttr = result[0]; 322 this.contextExpiry = result[1]; 323 auto securityStatus = result[2]; 324 this.context = result[3]; 325 326 if (securityStatus & SEC_I_COMPLETE_NEEDED || securityStatus & SEC_I_COMPLETE_AND_CONTINUE) 327 context.completeAuthToken(bufferDescOut); 328 this.isAuthenticated = (securityStatus ==0); 329 return tuple(securityStatus, buffers[0]); 330 331 } 332 }