Don Newton | 379ae25 | 2019-04-01 12:17:06 -0400 | [diff] [blame^] | 1 | // Copyright (C) MongoDB, Inc. 2017-present. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may |
| 4 | // not use this file except in compliance with the License. You may obtain |
| 5 | // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 |
| 6 | |
| 7 | //+build gssapi,windows |
| 8 | |
| 9 | package gssapi |
| 10 | |
| 11 | // #include "sspi_wrapper.h" |
| 12 | import "C" |
| 13 | import ( |
| 14 | "fmt" |
| 15 | "net" |
| 16 | "strconv" |
| 17 | "strings" |
| 18 | "sync" |
| 19 | "unsafe" |
| 20 | ) |
| 21 | |
| 22 | // New creates a new SaslClient. |
| 23 | func New(target, username, password string, passwordSet bool, props map[string]string) (*SaslClient, error) { |
| 24 | initOnce.Do(initSSPI) |
| 25 | if initError != nil { |
| 26 | return nil, initError |
| 27 | } |
| 28 | |
| 29 | var err error |
| 30 | serviceName := "mongodb" |
| 31 | serviceRealm := "" |
| 32 | canonicalizeHostName := false |
| 33 | |
| 34 | for key, value := range props { |
| 35 | switch strings.ToUpper(key) { |
| 36 | case "CANONICALIZE_HOST_NAME": |
| 37 | canonicalizeHostName, err = strconv.ParseBool(value) |
| 38 | if err != nil { |
| 39 | return nil, fmt.Errorf("%s must be a boolean (true, false, 0, 1) but got '%s'", key, value) |
| 40 | } |
| 41 | |
| 42 | case "SERVICE_REALM": |
| 43 | serviceRealm = value |
| 44 | case "SERVICE_NAME": |
| 45 | serviceName = value |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | hostname, _, err := net.SplitHostPort(target) |
| 50 | if err != nil { |
| 51 | return nil, fmt.Errorf("invalid endpoint (%s) specified: %s", target, err) |
| 52 | } |
| 53 | if canonicalizeHostName { |
| 54 | names, err := net.LookupAddr(hostname) |
| 55 | if err != nil || len(names) == 0 { |
| 56 | return nil, fmt.Errorf("unable to canonicalize hostname: %s", err) |
| 57 | } |
| 58 | hostname = names[0] |
| 59 | if hostname[len(hostname)-1] == '.' { |
| 60 | hostname = hostname[:len(hostname)-1] |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | servicePrincipalName := fmt.Sprintf("%s/%s", serviceName, hostname) |
| 65 | if serviceRealm != "" { |
| 66 | servicePrincipalName += "@" + serviceRealm |
| 67 | } |
| 68 | |
| 69 | return &SaslClient{ |
| 70 | servicePrincipalName: servicePrincipalName, |
| 71 | username: username, |
| 72 | password: password, |
| 73 | passwordSet: passwordSet, |
| 74 | }, nil |
| 75 | } |
| 76 | |
| 77 | type SaslClient struct { |
| 78 | servicePrincipalName string |
| 79 | username string |
| 80 | password string |
| 81 | passwordSet bool |
| 82 | |
| 83 | // state |
| 84 | state C.sspi_client_state |
| 85 | contextComplete bool |
| 86 | done bool |
| 87 | } |
| 88 | |
| 89 | func (sc *SaslClient) Close() { |
| 90 | C.sspi_client_destroy(&sc.state) |
| 91 | } |
| 92 | |
| 93 | func (sc *SaslClient) Start() (string, []byte, error) { |
| 94 | const mechName = "GSSAPI" |
| 95 | |
| 96 | var cusername *C.char |
| 97 | var cpassword *C.char |
| 98 | if sc.username != "" { |
| 99 | cusername = C.CString(sc.username) |
| 100 | defer C.free(unsafe.Pointer(cusername)) |
| 101 | if sc.passwordSet { |
| 102 | cpassword = C.CString(sc.password) |
| 103 | defer C.free(unsafe.Pointer(cpassword)) |
| 104 | } |
| 105 | } |
| 106 | status := C.sspi_client_init(&sc.state, cusername, cpassword) |
| 107 | |
| 108 | if status != C.SSPI_OK { |
| 109 | return mechName, nil, sc.getError("unable to intitialize client") |
| 110 | } |
| 111 | |
| 112 | return mechName, nil, nil |
| 113 | } |
| 114 | |
| 115 | func (sc *SaslClient) Next(challenge []byte) ([]byte, error) { |
| 116 | |
| 117 | var outBuf C.PVOID |
| 118 | var outBufLen C.ULONG |
| 119 | |
| 120 | if sc.contextComplete { |
| 121 | if sc.username == "" { |
| 122 | var cusername *C.char |
| 123 | status := C.sspi_client_username(&sc.state, &cusername) |
| 124 | if status != C.SSPI_OK { |
| 125 | return nil, sc.getError("unable to acquire username") |
| 126 | } |
| 127 | defer C.free(unsafe.Pointer(cusername)) |
| 128 | sc.username = C.GoString((*C.char)(unsafe.Pointer(cusername))) |
| 129 | } |
| 130 | |
| 131 | bytes := append([]byte{1, 0, 0, 0}, []byte(sc.username)...) |
| 132 | buf := (C.PVOID)(unsafe.Pointer(&bytes[0])) |
| 133 | bufLen := C.ULONG(len(bytes)) |
| 134 | status := C.sspi_client_wrap_msg(&sc.state, buf, bufLen, &outBuf, &outBufLen) |
| 135 | if status != C.SSPI_OK { |
| 136 | return nil, sc.getError("unable to wrap authz") |
| 137 | } |
| 138 | |
| 139 | sc.done = true |
| 140 | } else { |
| 141 | var buf C.PVOID |
| 142 | var bufLen C.ULONG |
| 143 | if len(challenge) > 0 { |
| 144 | buf = (C.PVOID)(unsafe.Pointer(&challenge[0])) |
| 145 | bufLen = C.ULONG(len(challenge)) |
| 146 | } |
| 147 | cservicePrincipalName := C.CString(sc.servicePrincipalName) |
| 148 | defer C.free(unsafe.Pointer(cservicePrincipalName)) |
| 149 | |
| 150 | status := C.sspi_client_negotiate(&sc.state, cservicePrincipalName, buf, bufLen, &outBuf, &outBufLen) |
| 151 | switch status { |
| 152 | case C.SSPI_OK: |
| 153 | sc.contextComplete = true |
| 154 | case C.SSPI_CONTINUE: |
| 155 | default: |
| 156 | return nil, sc.getError("unable to negotiate with server") |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | if outBuf != C.PVOID(nil) { |
| 161 | defer C.free(unsafe.Pointer(outBuf)) |
| 162 | } |
| 163 | |
| 164 | return C.GoBytes(unsafe.Pointer(outBuf), C.int(outBufLen)), nil |
| 165 | } |
| 166 | |
| 167 | func (sc *SaslClient) Completed() bool { |
| 168 | return sc.done |
| 169 | } |
| 170 | |
| 171 | func (sc *SaslClient) getError(prefix string) error { |
| 172 | return getError(prefix, sc.state.status) |
| 173 | } |
| 174 | |
| 175 | var initOnce sync.Once |
| 176 | var initError error |
| 177 | |
| 178 | func initSSPI() { |
| 179 | rc := C.sspi_init() |
| 180 | if rc != 0 { |
| 181 | initError = fmt.Errorf("error initializing sspi: %v", rc) |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | func getError(prefix string, status C.SECURITY_STATUS) error { |
| 186 | var s string |
| 187 | switch status { |
| 188 | case C.SEC_E_ALGORITHM_MISMATCH: |
| 189 | s = "The client and server cannot communicate because they do not possess a common algorithm." |
| 190 | case C.SEC_E_BAD_BINDINGS: |
| 191 | s = "The SSPI channel bindings supplied by the client are incorrect." |
| 192 | case C.SEC_E_BAD_PKGID: |
| 193 | s = "The requested package identifier does not exist." |
| 194 | case C.SEC_E_BUFFER_TOO_SMALL: |
| 195 | s = "The buffers supplied to the function are not large enough to contain the information." |
| 196 | case C.SEC_E_CANNOT_INSTALL: |
| 197 | s = "The security package cannot initialize successfully and should not be installed." |
| 198 | case C.SEC_E_CANNOT_PACK: |
| 199 | s = "The package is unable to pack the context." |
| 200 | case C.SEC_E_CERT_EXPIRED: |
| 201 | s = "The received certificate has expired." |
| 202 | case C.SEC_E_CERT_UNKNOWN: |
| 203 | s = "An unknown error occurred while processing the certificate." |
| 204 | case C.SEC_E_CERT_WRONG_USAGE: |
| 205 | s = "The certificate is not valid for the requested usage." |
| 206 | case C.SEC_E_CONTEXT_EXPIRED: |
| 207 | s = "The application is referencing a context that has already been closed. A properly written application should not receive this error." |
| 208 | case C.SEC_E_CROSSREALM_DELEGATION_FAILURE: |
| 209 | s = "The server attempted to make a Kerberos-constrained delegation request for a target outside the server's realm." |
| 210 | case C.SEC_E_CRYPTO_SYSTEM_INVALID: |
| 211 | s = "The cryptographic system or checksum function is not valid because a required function is unavailable." |
| 212 | case C.SEC_E_DECRYPT_FAILURE: |
| 213 | s = "The specified data could not be decrypted." |
| 214 | case C.SEC_E_DELEGATION_REQUIRED: |
| 215 | s = "The requested operation cannot be completed. The computer must be trusted for delegation" |
| 216 | case C.SEC_E_DOWNGRADE_DETECTED: |
| 217 | s = "The system detected a possible attempt to compromise security. Verify that the server that authenticated you can be contacted." |
| 218 | case C.SEC_E_ENCRYPT_FAILURE: |
| 219 | s = "The specified data could not be encrypted." |
| 220 | case C.SEC_E_ILLEGAL_MESSAGE: |
| 221 | s = "The message received was unexpected or badly formatted." |
| 222 | case C.SEC_E_INCOMPLETE_CREDENTIALS: |
| 223 | s = "The credentials supplied were not complete and could not be verified. The context could not be initialized." |
| 224 | case C.SEC_E_INCOMPLETE_MESSAGE: |
| 225 | s = "The message supplied was incomplete. The signature was not verified." |
| 226 | case C.SEC_E_INSUFFICIENT_MEMORY: |
| 227 | s = "Not enough memory is available to complete the request." |
| 228 | case C.SEC_E_INTERNAL_ERROR: |
| 229 | s = "An error occurred that did not map to an SSPI error code." |
| 230 | case C.SEC_E_INVALID_HANDLE: |
| 231 | s = "The handle passed to the function is not valid." |
| 232 | case C.SEC_E_INVALID_TOKEN: |
| 233 | s = "The token passed to the function is not valid." |
| 234 | case C.SEC_E_ISSUING_CA_UNTRUSTED: |
| 235 | s = "An untrusted certification authority (CA) was detected while processing the smart card certificate used for authentication." |
| 236 | case C.SEC_E_ISSUING_CA_UNTRUSTED_KDC: |
| 237 | s = "An untrusted CA was detected while processing the domain controller certificate used for authentication. The system event log contains additional information." |
| 238 | case C.SEC_E_KDC_CERT_EXPIRED: |
| 239 | s = "The domain controller certificate used for smart card logon has expired." |
| 240 | case C.SEC_E_KDC_CERT_REVOKED: |
| 241 | s = "The domain controller certificate used for smart card logon has been revoked." |
| 242 | case C.SEC_E_KDC_INVALID_REQUEST: |
| 243 | s = "A request that is not valid was sent to the KDC." |
| 244 | case C.SEC_E_KDC_UNABLE_TO_REFER: |
| 245 | s = "The KDC was unable to generate a referral for the service requested." |
| 246 | case C.SEC_E_KDC_UNKNOWN_ETYPE: |
| 247 | s = "The requested encryption type is not supported by the KDC." |
| 248 | case C.SEC_E_LOGON_DENIED: |
| 249 | s = "The logon has been denied" |
| 250 | case C.SEC_E_MAX_REFERRALS_EXCEEDED: |
| 251 | s = "The number of maximum ticket referrals has been exceeded." |
| 252 | case C.SEC_E_MESSAGE_ALTERED: |
| 253 | s = "The message supplied for verification has been altered." |
| 254 | case C.SEC_E_MULTIPLE_ACCOUNTS: |
| 255 | s = "The received certificate was mapped to multiple accounts." |
| 256 | case C.SEC_E_MUST_BE_KDC: |
| 257 | s = "The local computer must be a Kerberos domain controller (KDC)" |
| 258 | case C.SEC_E_NO_AUTHENTICATING_AUTHORITY: |
| 259 | s = "No authority could be contacted for authentication." |
| 260 | case C.SEC_E_NO_CREDENTIALS: |
| 261 | s = "No credentials are available." |
| 262 | case C.SEC_E_NO_IMPERSONATION: |
| 263 | s = "No impersonation is allowed for this context." |
| 264 | case C.SEC_E_NO_IP_ADDRESSES: |
| 265 | s = "Unable to accomplish the requested task because the local computer does not have any IP addresses." |
| 266 | case C.SEC_E_NO_KERB_KEY: |
| 267 | s = "No Kerberos key was found." |
| 268 | case C.SEC_E_NO_PA_DATA: |
| 269 | s = "Policy administrator (PA) data is needed to determine the encryption type" |
| 270 | case C.SEC_E_NO_S4U_PROT_SUPPORT: |
| 271 | s = "The Kerberos subsystem encountered an error. A service for user protocol request was made against a domain controller which does not support service for a user." |
| 272 | case C.SEC_E_NO_TGT_REPLY: |
| 273 | s = "The client is trying to negotiate a context and the server requires a user-to-user connection" |
| 274 | case C.SEC_E_NOT_OWNER: |
| 275 | s = "The caller of the function does not own the credentials." |
| 276 | case C.SEC_E_OK: |
| 277 | s = "The operation completed successfully." |
| 278 | case C.SEC_E_OUT_OF_SEQUENCE: |
| 279 | s = "The message supplied for verification is out of sequence." |
| 280 | case C.SEC_E_PKINIT_CLIENT_FAILURE: |
| 281 | s = "The smart card certificate used for authentication is not trusted." |
| 282 | case C.SEC_E_PKINIT_NAME_MISMATCH: |
| 283 | s = "The client certificate does not contain a valid UPN or does not match the client name in the logon request." |
| 284 | case C.SEC_E_QOP_NOT_SUPPORTED: |
| 285 | s = "The quality of protection attribute is not supported by this package." |
| 286 | case C.SEC_E_REVOCATION_OFFLINE_C: |
| 287 | s = "The revocation status of the smart card certificate used for authentication could not be determined." |
| 288 | case C.SEC_E_REVOCATION_OFFLINE_KDC: |
| 289 | s = "The revocation status of the domain controller certificate used for smart card authentication could not be determined. The system event log contains additional information." |
| 290 | case C.SEC_E_SECPKG_NOT_FOUND: |
| 291 | s = "The security package was not recognized." |
| 292 | case C.SEC_E_SECURITY_QOS_FAILED: |
| 293 | s = "The security context could not be established due to a failure in the requested quality of service (for example" |
| 294 | case C.SEC_E_SHUTDOWN_IN_PROGRESS: |
| 295 | s = "A system shutdown is in progress." |
| 296 | case C.SEC_E_SMARTCARD_CERT_EXPIRED: |
| 297 | s = "The smart card certificate used for authentication has expired." |
| 298 | case C.SEC_E_SMARTCARD_CERT_REVOKED: |
| 299 | s = "The smart card certificate used for authentication has been revoked. Additional information may exist in the event log." |
| 300 | case C.SEC_E_SMARTCARD_LOGON_REQUIRED: |
| 301 | s = "Smart card logon is required and was not used." |
| 302 | case C.SEC_E_STRONG_CRYPTO_NOT_SUPPORTED: |
| 303 | s = "The other end of the security negotiation requires strong cryptography" |
| 304 | case C.SEC_E_TARGET_UNKNOWN: |
| 305 | s = "The target was not recognized." |
| 306 | case C.SEC_E_TIME_SKEW: |
| 307 | s = "The clocks on the client and server computers do not match." |
| 308 | case C.SEC_E_TOO_MANY_PRINCIPALS: |
| 309 | s = "The KDC reply contained more than one principal name." |
| 310 | case C.SEC_E_UNFINISHED_CONTEXT_DELETED: |
| 311 | s = "A security context was deleted before the context was completed. This is considered a logon failure." |
| 312 | case C.SEC_E_UNKNOWN_CREDENTIALS: |
| 313 | s = "The credentials provided were not recognized." |
| 314 | case C.SEC_E_UNSUPPORTED_FUNCTION: |
| 315 | s = "The requested function is not supported." |
| 316 | case C.SEC_E_UNSUPPORTED_PREAUTH: |
| 317 | s = "An unsupported preauthentication mechanism was presented to the Kerberos package." |
| 318 | case C.SEC_E_UNTRUSTED_ROOT: |
| 319 | s = "The certificate chain was issued by an authority that is not trusted." |
| 320 | case C.SEC_E_WRONG_CREDENTIAL_HANDLE: |
| 321 | s = "The supplied credential handle does not match the credential associated with the security context." |
| 322 | case C.SEC_E_WRONG_PRINCIPAL: |
| 323 | s = "The target principal name is incorrect." |
| 324 | case C.SEC_I_COMPLETE_AND_CONTINUE: |
| 325 | s = "The function completed successfully" |
| 326 | case C.SEC_I_COMPLETE_NEEDED: |
| 327 | s = "The function completed successfully" |
| 328 | case C.SEC_I_CONTEXT_EXPIRED: |
| 329 | s = "The message sender has finished using the connection and has initiated a shutdown. For information about initiating or recognizing a shutdown" |
| 330 | case C.SEC_I_CONTINUE_NEEDED: |
| 331 | s = "The function completed successfully" |
| 332 | case C.SEC_I_INCOMPLETE_CREDENTIALS: |
| 333 | s = "The credentials supplied were not complete and could not be verified. Additional information can be returned from the context." |
| 334 | case C.SEC_I_LOCAL_LOGON: |
| 335 | s = "The logon was completed" |
| 336 | case C.SEC_I_NO_LSA_CONTEXT: |
| 337 | s = "There is no LSA mode context associated with this context." |
| 338 | case C.SEC_I_RENEGOTIATE: |
| 339 | s = "The context data must be renegotiated with the peer." |
| 340 | default: |
| 341 | return fmt.Errorf("%s: 0x%x", prefix, uint32(status)) |
| 342 | } |
| 343 | |
| 344 | return fmt.Errorf("%s: %s(0x%x)", prefix, s, uint32(status)) |
| 345 | } |