static int AuthenticationDialogue(ServerConnectionState *conn, char *recvbuffer, int recvlen) { char in[CF_BUFSIZE], *out, *decrypted_nonce; BIGNUM *counter_challenge = NULL; unsigned char digest[EVP_MAX_MD_SIZE + 1] = { 0 }; unsigned int crypt_len, nonce_len = 0, encrypted_len = 0; char sauth[10], iscrypt = 'n', enterprise_field = 'c'; int len_n = 0, len_e = 0, keylen, session_size; unsigned long err; RSA *newkey; int digestLen = 0; HashMethod digestType; if ((PRIVKEY == NULL) || (PUBKEY == NULL)) { Log(LOG_LEVEL_ERR, "No public/private key pair exists, create one with cf-key"); return false; } if (FIPS_MODE) { digestType = CF_DEFAULT_DIGEST; digestLen = CF_DEFAULT_DIGEST_LEN; } else { digestType = HASH_METHOD_MD5; digestLen = CF_MD5_LEN; } /* proposition C1 */ /* Opening string is a challenge from the client (some agent) */ sauth[0] = '\0'; sscanf(recvbuffer, "%s %c %u %u %c", sauth, &iscrypt, &crypt_len, &nonce_len, &enterprise_field); if ((crypt_len == 0) || (nonce_len == 0) || (strlen(sauth) == 0)) { Log(LOG_LEVEL_INFO, "Protocol format error in authentation from IP %s", conn->hostname); return false; } if (nonce_len > CF_NONCELEN * 2) { Log(LOG_LEVEL_INFO, "Protocol deviant authentication nonce from %s", conn->hostname); return false; } if (crypt_len > 2 * CF_NONCELEN) { Log(LOG_LEVEL_INFO, "Protocol abuse in unlikely cipher from %s", conn->hostname); return false; } /* Check there is no attempt to read past the end of the received input */ if (recvbuffer + CF_RSA_PROTO_OFFSET + nonce_len > recvbuffer + recvlen) { Log(LOG_LEVEL_INFO, "Protocol consistency error in authentication from %s", conn->hostname); return false; } if ((strcmp(sauth, "SAUTH") != 0) || (nonce_len == 0) || (crypt_len == 0)) { Log(LOG_LEVEL_INFO, "Protocol error in RSA authentication from IP '%s'", conn->hostname); return false; } Log(LOG_LEVEL_DEBUG, "Challenge encryption = %c, nonce = %d, buf = %d", iscrypt, nonce_len, crypt_len); decrypted_nonce = xmalloc(crypt_len); if (iscrypt == 'y') { if (RSA_private_decrypt (crypt_len, recvbuffer + CF_RSA_PROTO_OFFSET, decrypted_nonce, PRIVKEY, RSA_PKCS1_PADDING) <= 0) { err = ERR_get_error(); Log(LOG_LEVEL_ERR, "Private decrypt failed = '%s'. Probably the client has the wrong public key for this server", ERR_reason_error_string(err)); free(decrypted_nonce); return false; } } else { if (nonce_len > crypt_len) { Log(LOG_LEVEL_ERR, "Illegal challenge"); free(decrypted_nonce); return false; } memcpy(decrypted_nonce, recvbuffer + CF_RSA_PROTO_OFFSET, nonce_len); } /* Client's ID is now established by key or trusted, reply with digest */ HashString(decrypted_nonce, nonce_len, digest, digestType); free(decrypted_nonce); /* Get the public key from the client */ newkey = RSA_new(); /* proposition C2 */ if ((len_n = ReceiveTransaction(conn->conn_info, recvbuffer, NULL)) == -1) { Log(LOG_LEVEL_INFO, "Protocol error 1 in RSA authentation from IP %s", conn->hostname); RSA_free(newkey); return false; } if (len_n == 0) { Log(LOG_LEVEL_INFO, "Protocol error 2 in RSA authentation from IP %s", conn->hostname); RSA_free(newkey); return false; } if ((newkey->n = BN_mpi2bn(recvbuffer, len_n, NULL)) == NULL) { err = ERR_get_error(); Log(LOG_LEVEL_ERR, "Private decrypt failed = %s", ERR_reason_error_string(err)); RSA_free(newkey); return false; } /* proposition C3 */ if ((len_e = ReceiveTransaction(conn->conn_info, recvbuffer, NULL)) == -1) { Log(LOG_LEVEL_INFO, "Protocol error 3 in RSA authentation from IP %s", conn->hostname); RSA_free(newkey); return false; } if (len_e == 0) { Log(LOG_LEVEL_INFO, "Protocol error 4 in RSA authentation from IP %s", conn->hostname); RSA_free(newkey); return false; } if ((newkey->e = BN_mpi2bn(recvbuffer, len_e, NULL)) == NULL) { err = ERR_get_error(); Log(LOG_LEVEL_ERR, "Private decrypt failed = %s", ERR_reason_error_string(err)); RSA_free(newkey); return false; } /* Compute and store hash of the client's public key. */ Key *key = KeyNew(newkey, CF_DEFAULT_DIGEST); ConnectionInfoSetKey(conn->conn_info, key); Log(LOG_LEVEL_VERBOSE, "Public key identity of host '%s' is '%s'", conn->ipaddr, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); LastSaw1(conn->ipaddr, KeyPrintableHash(ConnectionInfoKey(conn->conn_info)), LAST_SEEN_ROLE_ACCEPT); if (!CheckStoreKey(conn, newkey)) /* conceals proposition S1 */ { return false; } /* Reply with digest of original challenge */ /* proposition S2 */ SendTransaction(conn->conn_info, digest, digestLen, CF_DONE); /* Send counter challenge to be sure this is a live session */ counter_challenge = BN_new(); if (counter_challenge == NULL) { Log(LOG_LEVEL_ERR, "Cannot allocate BIGNUM structure for counter challenge"); return false; } BN_rand(counter_challenge, CF_NONCELEN, 0, 0); nonce_len = BN_bn2mpi(counter_challenge, in); // hash the challenge from the client HashString(in, nonce_len, digest, digestType); encrypted_len = RSA_size(newkey); /* encryption buffer is always the same size as n */ out = xmalloc(encrypted_len + 1); if (RSA_public_encrypt(nonce_len, in, out, newkey, RSA_PKCS1_PADDING) <= 0) { err = ERR_get_error(); Log(LOG_LEVEL_ERR, "Public encryption failed = %s", ERR_reason_error_string(err)); free(out); return false; } /* proposition S3 */ SendTransaction(conn->conn_info, out, encrypted_len, CF_DONE); /* if the client doesn't have our public key, send it */ if (iscrypt != 'y') { /* proposition S4 - conditional */ memset(in, 0, CF_BUFSIZE); len_n = BN_bn2mpi(PUBKEY->n, in); SendTransaction(conn->conn_info, in, len_n, CF_DONE); /* proposition S5 - conditional */ memset(in, 0, CF_BUFSIZE); len_e = BN_bn2mpi(PUBKEY->e, in); SendTransaction(conn->conn_info, in, len_e, CF_DONE); } /* Receive reply to counter_challenge */ /* proposition C4 */ memset(in, 0, CF_BUFSIZE); if (ReceiveTransaction(conn->conn_info, in, NULL) == -1) { BN_free(counter_challenge); free(out); return false; } if (HashesMatch(digest, in, digestType)) /* replay / piggy in the middle attack ? */ { Log(LOG_LEVEL_VERBOSE, "Authentication of client %s/%s achieved", conn->hostname, conn->ipaddr); } else { BN_free(counter_challenge); free(out); Log(LOG_LEVEL_INFO, "Challenge response from client %s was incorrect - ID false?", conn->ipaddr); return false; } /* Receive random session key,... */ /* proposition C5 */ memset(in, 0, CF_BUFSIZE); if ((keylen = ReceiveTransaction(conn->conn_info, in, NULL)) == -1) { BN_free(counter_challenge); free(out); return false; } if (keylen > CF_BUFSIZE / 2) { BN_free(counter_challenge); free(out); Log(LOG_LEVEL_INFO, "Session key length received from %s is too long", conn->ipaddr); return false; } session_size = CfSessionKeySize(enterprise_field); conn->session_key = xmalloc(session_size); conn->encryption_type = enterprise_field; Log(LOG_LEVEL_VERBOSE, "Receiving session key from client (size=%d)...", keylen); Log(LOG_LEVEL_DEBUG, "keylen = %d, session_size = %d", keylen, session_size); if (keylen == CF_BLOWFISHSIZE) /* Support the old non-ecnrypted for upgrade */ { memcpy(conn->session_key, in, session_size); } else { /* New protocol encrypted */ if (RSA_private_decrypt(keylen, in, out, PRIVKEY, RSA_PKCS1_PADDING) <= 0) { err = ERR_get_error(); Log(LOG_LEVEL_ERR, "Private decrypt failed = %s", ERR_reason_error_string(err)); BN_free(counter_challenge); free(out); return false; } memcpy(conn->session_key, out, session_size); } BN_free(counter_challenge); free(out); return true; }
/** * @brief Accept a TLS connection and authenticate and identify. * @note Various fields in #conn are set, like username and keyhash. */ int ServerTLSSessionEstablish(ServerConnectionState *conn) { int ret; conn->conn_info.ssl = SSL_new(SSLSERVERCONTEXT); if (conn->conn_info.ssl == NULL) { Log(LOG_LEVEL_ERR, "SSL_new: %s", ERR_reason_error_string(ERR_get_error())); return -1; } /* Now we are letting OpenSSL take over the open socket. */ SSL_set_fd(conn->conn_info.ssl, conn->conn_info.sd); ret = SSL_accept(conn->conn_info.ssl); if (ret <= 0) { TLSLogError(conn->conn_info.ssl, LOG_LEVEL_ERR, "Connection handshake", ret); return -1; } Log(LOG_LEVEL_VERBOSE, "TLS cipher negotiated: %s, %s", SSL_get_cipher_name(conn->conn_info.ssl), SSL_get_cipher_version(conn->conn_info.ssl)); Log(LOG_LEVEL_VERBOSE, "TLS session established, checking trust..."); /* Send/Receive "CFE_v%d" version string and agree on version. */ ret = ServerNegotiateProtocol(&conn->conn_info); if (ret <= 0) { return -1; } /* Receive IDENTITY USER=asdf plain string. */ ret = ServerIdentifyClient(&conn->conn_info, conn->username, sizeof(conn->username)); if (ret != 1) { return -1; } /* We *now* (maybe a bit late) verify the key that the client sent us in * the TLS handshake, since we need the username to do so. TODO in the * future store keys irrelevant of username, so that we can match them * before IDENTIFY. */ ret = TLSVerifyPeer(&conn->conn_info, conn->ipaddr, conn->username); if (ret == -1) /* error */ { return -1; } if (ret == 1) /* trusted key */ { Log(LOG_LEVEL_VERBOSE, "%s: Client is TRUSTED, public key MATCHES stored one.", conn->conn_info.remote_keyhash_str); } if (ret == 0) /* untrusted key */ { Log(LOG_LEVEL_WARNING, "%s: Client's public key is UNKNOWN!", conn->conn_info.remote_keyhash_str); if ((SV.trustkeylist != NULL) && (IsMatchItemIn(conn->ctx, SV.trustkeylist, MapAddress(conn->ipaddr)))) { Log(LOG_LEVEL_VERBOSE, "Host %s was found in the \"trustkeysfrom\" list", conn->ipaddr); Log(LOG_LEVEL_WARNING, "%s: Explicitly trusting this key from now on.", conn->conn_info.remote_keyhash_str); conn->trust = true; SavePublicKey("root", conn->conn_info.remote_keyhash_str, conn->conn_info.remote_key); } else { Log(LOG_LEVEL_ERR, "TRUST FAILED, WARNING: possible MAN IN THE MIDDLE attack, dropping connection!"); Log(LOG_LEVEL_ERR, "Open server's ACL if you really want to start trusting this new key."); return -1; } } /* skipping CAUTH */ conn->id_verified = 1; /* skipping SAUTH, allow access to read-only files */ conn->rsa_auth = 1; LastSaw1(conn->ipaddr, conn->conn_info.remote_keyhash_str, LAST_SEEN_ROLE_ACCEPT); ServerSendWelcome(conn); return 1; }
/** * @brief Accept a TLS connection and authenticate and identify. * @note Various fields in #conn are set, like username and keyhash. */ int ServerTLSSessionEstablish(ServerConnectionState *conn) { int ret; if (ConnectionInfoConnectionStatus(conn->conn_info) != CF_CONNECTION_ESTABLISHED) { assert(ConnectionInfoSSL(conn->conn_info) == NULL); SSL *ssl = SSL_new(SSLSERVERCONTEXT); if (ssl == NULL) { Log(LOG_LEVEL_ERR, "SSL_new: %s", TLSErrorString(ERR_get_error())); return -1; } ConnectionInfoSetSSL(conn->conn_info, ssl); /* Pass conn_info inside the ssl struct for TLSVerifyCallback(). */ SSL_set_ex_data(ssl, CONNECTIONINFO_SSL_IDX, conn->conn_info); /* Now we are letting OpenSSL take over the open socket. */ SSL_set_fd(ssl, ConnectionInfoSocket(conn->conn_info)); ret = SSL_accept(ssl); if (ret <= 0) { TLSLogError(ssl, LOG_LEVEL_ERR, "Failed to accept TLS connection", ret); return -1; } Log(LOG_LEVEL_VERBOSE, "TLS cipher negotiated: %s, %s", SSL_get_cipher_name(ssl), SSL_get_cipher_version(ssl)); Log(LOG_LEVEL_VERBOSE, "TLS session established, checking trust..."); /* Send/Receive "CFE_v%d" version string, agree on version, receive identity (username) of peer. */ char username[sizeof(conn->username)] = ""; bool b = ServerIdentificationDialog(conn->conn_info, username, sizeof(username)); if (b != true) { return -1; } /* We *now* (maybe a bit late) verify the key that the client sent us in * the TLS handshake, since we need the username to do so. TODO in the * future store keys irrelevant of username, so that we can match them * before IDENTIFY. */ ret = TLSVerifyPeer(conn->conn_info, conn->ipaddr, username); if (ret == -1) /* error */ { return -1; } if (ret == 1) /* trusted key */ { Log(LOG_LEVEL_VERBOSE, "%s: Client is TRUSTED, public key MATCHES stored one.", KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); } if (ret == 0) /* untrusted key */ { if ((SV.trustkeylist != NULL) && (IsMatchItemIn(SV.trustkeylist, conn->ipaddr))) { Log(LOG_LEVEL_VERBOSE, "Peer was found in \"trustkeysfrom\" list"); Log(LOG_LEVEL_NOTICE, "Trusting new key: %s", KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); SavePublicKey(username, KeyPrintableHash(conn->conn_info->remote_key), KeyRSA(ConnectionInfoKey(conn->conn_info))); } else { Log(LOG_LEVEL_NOTICE, "TRUST FAILED, peer presented an untrusted key, dropping connection!"); Log(LOG_LEVEL_VERBOSE, "Add peer to \"trustkeysfrom\" if you really want to start trusting this new key."); return -1; } } /* All checks succeeded, set conn->uid (conn->sid for Windows) * according to the received USERNAME identity. */ SetConnIdentity(conn, username); /* No CAUTH, SAUTH in non-classic protocol. */ conn->user_data_set = 1; conn->rsa_auth = 1; LastSaw1(conn->ipaddr, KeyPrintableHash(ConnectionInfoKey(conn->conn_info)), LAST_SEEN_ROLE_ACCEPT); ServerSendWelcome(conn); } return 1; }
/** * @NOTE if #flags.protocol_version is CF_PROTOCOL_UNDEFINED, then classic * protocol is used by default. */ AgentConnection *ServerConnection(const char *server, const char *port, unsigned int connect_timeout, ConnectionFlags flags, int *err) { AgentConnection *conn = NULL; int ret; *err = 0; conn = NewAgentConn(server, port, flags); #if !defined(__MINGW32__) signal(SIGPIPE, SIG_IGN); sigset_t signal_mask; sigemptyset(&signal_mask); sigaddset(&signal_mask, SIGPIPE); pthread_sigmask(SIG_BLOCK, &signal_mask, NULL); /* FIXME: username is local */ GetCurrentUserName(conn->username, sizeof(conn->username)); #else /* Always say "root" as username from windows. */ strlcpy(conn->username, "root", sizeof(conn->username)); #endif if (port == NULL || *port == '\0') { port = CFENGINE_PORT_STR; } char txtaddr[CF_MAX_IP_LEN] = ""; conn->conn_info->sd = SocketConnect(server, port, connect_timeout, flags.force_ipv4, txtaddr, sizeof(txtaddr)); if (conn->conn_info->sd == -1) { Log(LOG_LEVEL_INFO, "No server is responding on port: %s", port); DisconnectServer(conn); *err = -1; return NULL; } assert(sizeof(conn->remoteip) >= sizeof(txtaddr)); strcpy(conn->remoteip, txtaddr); switch (flags.protocol_version) { case CF_PROTOCOL_UNDEFINED: case CF_PROTOCOL_TLS: /* Set the version to request during protocol negotiation. After * TLSConnect() it will have the version we finally ended up with. */ conn->conn_info->protocol = CF_PROTOCOL_LATEST; ret = TLSConnect(conn->conn_info, flags.trust_server, conn->remoteip, conn->username); if (ret == -1) /* Error */ { DisconnectServer(conn); *err = -1; return NULL; } else if (ret == 0) /* Auth/ID error */ { DisconnectServer(conn); errno = EPERM; *err = -2; return NULL; } assert(ret == 1); conn->conn_info->status = CONNECTIONINFO_STATUS_ESTABLISHED; LastSaw1(conn->remoteip, KeyPrintableHash(conn->conn_info->remote_key), LAST_SEEN_ROLE_CONNECT); break; case CF_PROTOCOL_CLASSIC: conn->conn_info->protocol = CF_PROTOCOL_CLASSIC; conn->encryption_type = CfEnterpriseOptions(); if (!IdentifyAgent(conn->conn_info)) { Log(LOG_LEVEL_ERR, "Id-authentication for '%s' failed", VFQNAME); errno = EPERM; DisconnectServer(conn); *err = -2; // auth err return NULL; } if (!AuthenticateAgent(conn, flags.trust_server)) { Log(LOG_LEVEL_ERR, "Authentication dialogue with '%s' failed", server); errno = EPERM; DisconnectServer(conn); *err = -2; // auth err return NULL; } conn->conn_info->status = CONNECTIONINFO_STATUS_ESTABLISHED; break; default: ProgrammingError("ServerConnection: ProtocolVersion %d!", flags.protocol_version); } conn->authenticated = true; return conn; }