Selaa lähdekoodia

OpenVPN client certificate authentication (Individual Certificate Authentication) (#327)

* Implement OpenVPN certificate authentication, fixes #55

* fixup! Implement OpenVPN certificate authentication, fixes #55
Alexey Kryuchkov 7 vuotta sitten
vanhempi
sitoutus
83295bb736

+ 1 - 0
src/Cedar/Cedar.h

@@ -448,6 +448,7 @@
 #define	AUTHTYPE_ROOTCERT				3			// Root certificate which is issued by trusted Certificate Authority
 #define	AUTHTYPE_RADIUS					4			// Radius authentication
 #define	AUTHTYPE_NT						5			// Windows NT authentication
+#define	AUTHTYPE_OPENVPN_CERT    		98			// TLS client certificate authentication
 #define	AUTHTYPE_TICKET					99			// Ticket authentication
 
 // Constant of the client side

+ 1 - 1
src/Cedar/IPsec_EtherIP.c

@@ -170,7 +170,7 @@ void EtherIPIpcConnectThread(THREAD *t, void *p)
 			&s->ClientIP, s->ClientPort,
 			&s->ServerIP, s->ServerPort,
 			tmp,
-			s->CryptName, true, mss, NULL);
+			s->CryptName, true, mss, NULL, NULL);
 
 		if (ipc != NULL)
 		{

+ 10 - 3
src/Cedar/IPsec_IPC.c

@@ -323,7 +323,7 @@ IPC *NewIPCByParam(CEDAR *cedar, IPC_PARAM *param, UINT *error_code)
 		param->UserName, param->Password, error_code, &param->ClientIp,
 		param->ClientPort, &param->ServerIp, param->ServerPort,
 		param->ClientHostname, param->CryptName,
-		param->BridgeMode, param->Mss, NULL);
+		param->BridgeMode, param->Mss, NULL, param->ClientCertificate);
 
 	return ipc;
 }
@@ -332,7 +332,7 @@ IPC *NewIPCByParam(CEDAR *cedar, IPC_PARAM *param, UINT *error_code)
 IPC *NewIPC(CEDAR *cedar, char *client_name, char *postfix, char *hubname, char *username, char *password,
 			UINT *error_code, IP *client_ip, UINT client_port, IP *server_ip, UINT server_port,
 			char *client_hostname, char *crypt_name,
-			bool bridge_mode, UINT mss, EAP_CLIENT *eap_client)
+			bool bridge_mode, UINT mss, EAP_CLIENT *eap_client, X *client_certificate)
 {
 	IPC *ipc;
 	UINT dummy_int = 0;
@@ -425,7 +425,14 @@ IPC *NewIPC(CEDAR *cedar, char *client_name, char *postfix, char *hubname, char
 	FreePack(p);
 
 	// Upload the authentication data
-	p = PackLoginWithPlainPassword(hubname, username, password);
+	if (client_certificate != NULL)
+	{
+		p = PackLoginWithOpenVPNCertificate(hubname, username, client_certificate);
+	}
+	else
+	{
+		p = PackLoginWithPlainPassword(hubname, username, password);
+	}
 	PackAddStr(p, "hello", client_name);
 	PackAddInt(p, "client_ver", cedar->Version);
 	PackAddInt(p, "client_build", cedar->Build);

+ 2 - 1
src/Cedar/IPsec_IPC.h

@@ -165,6 +165,7 @@ struct IPC_PARAM
 	UINT Mss;
 	bool IsL3Mode;
 	bool IsOpenVPN;
+	X *ClientCertificate;
 };
 
 // IPC_ASYNC object
@@ -224,7 +225,7 @@ struct IPC_MSCHAP_V2_AUTHINFO
 IPC *NewIPC(CEDAR *cedar, char *client_name, char *postfix, char *hubname, char *username, char *password,
 			UINT *error_code, IP *client_ip, UINT client_port, IP *server_ip, UINT server_port,
 			char *client_hostname, char *crypt_name,
-			bool bridge_mode, UINT mss, EAP_CLIENT *eap_client);
+			bool bridge_mode, UINT mss, EAP_CLIENT *eap_client, X *client_certificate);
 IPC *NewIPCByParam(CEDAR *cedar, IPC_PARAM *param, UINT *error_code);
 IPC *NewIPCBySock(CEDAR *cedar, SOCK *s, void *mac_address);
 void FreeIPC(IPC *ipc);

+ 2 - 2
src/Cedar/IPsec_PPP.c

@@ -1018,7 +1018,7 @@ PPP_PACKET *PPPProcessRequestPacket(PPP_SESSION *p, PPP_PACKET *req)
 					// Attempt to connect with IPC
 					ipc = NewIPC(p->Cedar, p->ClientSoftwareName, p->Postfix, hub, id, password,
 						&error_code, &p->ClientIP, p->ClientPort, &p->ServerIP, p->ServerPort,
-						p->ClientHostname, p->CryptName, false, p->AdjustMss, p->EapClient);
+						p->ClientHostname, p->CryptName, false, p->AdjustMss, p->EapClient, NULL);
 
 					if (ipc != NULL)
 					{
@@ -1151,7 +1151,7 @@ PPP_PACKET *PPPProcessRequestPacket(PPP_SESSION *p, PPP_PACKET *req)
 
 								ipc = NewIPC(p->Cedar, p->ClientSoftwareName, p->Postfix, hub, id, password,
 									&error_code, &p->ClientIP, p->ClientPort, &p->ServerIP, p->ServerPort,
-									p->ClientHostname, p->CryptName, false, p->AdjustMss, NULL);
+									p->ClientHostname, p->CryptName, false, p->AdjustMss, NULL, NULL);
 
 								if (ipc != NULL)
 								{

+ 32 - 1
src/Cedar/Interop_OpenVPN.c

@@ -442,7 +442,8 @@ void OvsProcessRecvControlPacket(OPENVPN_SERVER *s, OPENVPN_SESSION *se, OPENVPN
 			// Create an SSL pipe
 			Lock(s->Cedar->lock);
 			{
-				c->SslPipe = NewSslPipe(true, s->Cedar->ServerX, s->Cedar->ServerK, s->Dh);
+				bool cert_verify = true;
+				c->SslPipe = NewSslPipeEx(true, s->Cedar->ServerX, s->Cedar->ServerK, s->Dh, cert_verify, &c->ClientCert);
 			}
 			Unlock(s->Cedar->lock);
 
@@ -712,6 +713,11 @@ void OvsBeginIPCAsyncConnectionIfEmpty(OPENVPN_SERVER *s, OPENVPN_SESSION *se, O
 			p.BridgeMode = true;
 		}
 
+		if (c->ClientCert.X != NULL)
+		{
+			p.ClientCertificate = c->ClientCert.X;
+		}
+
 		p.IsOpenVPN = true;
 
 		// Calculate the MSS
@@ -780,6 +786,26 @@ void OvsSetupSessionParameters(OPENVPN_SERVER *s, OPENVPN_SESSION *se, OPENVPN_C
 
 	OvsLog(s, se, c, "LO_OPTION_STR_RECV", data->OptionString);
 
+	if (c->ClientCert.X != NULL)
+	{
+		if (c->ClientCert.X->subject_name != NULL)
+		{
+			OvsLog(s, se, c, "LO_CLIENT_CERT", c->ClientCert.X->subject_name->CommonName);
+		}
+		else
+		{
+			OvsLog(s, se, c, "LO_CLIENT_CERT", "(unknown CN)");
+		}
+	}
+	else if (!c->ClientCert.PreverifyErr)
+	{
+		OvsLog(s, se, c, "LO_CLIENT_NO_CERT");
+	}
+	else
+	{
+		OvsLog(s, se, c, "LO_CLIENT_UNVERIFIED_CERT", c->ClientCert.PreverifyErrMessage);
+	}
+
 	Zero(opt_str, sizeof(opt_str));
 	StrCpy(opt_str, sizeof(opt_str), data->OptionString);
 	if (s->Cedar != NULL && (IsEmptyStr(opt_str) || StartWith(opt_str, "V0 UNDEF") || InStr(opt_str, ",") == false))
@@ -1359,6 +1385,11 @@ void OvsFreeChannel(OPENVPN_CHANNEL *c)
 	FreeMd(c->MdRecv);
 	FreeMd(c->MdSend);
 
+	if (c->ClientCert.X != NULL)
+	{
+		FreeX(c->ClientCert.X);
+	}
+
 	Free(c);
 }
 

+ 1 - 0
src/Cedar/Interop_OpenVPN.h

@@ -257,6 +257,7 @@ struct OPENVPN_CHANNEL
 	bool IsInitiatorServer;								// Whether the channel was started from the server side
 	bool RekeyInitiated;								// Whether re-keying has already started
 	UINT64 NextRekey;
+	struct SslClientCertInfo ClientCert;                // Client certificate and verification data
 };
 
 // OpenVPN session

+ 86 - 0
src/Cedar/Protocol.c

@@ -1795,6 +1795,9 @@ bool ServerAccept(CONNECTION *c)
 				case AUTHTYPE_TICKET:
 					authtype_str = _UU("LH_AUTH_TICKET");
 					break;
+				case AUTHTYPE_OPENVPN_CERT:
+					authtype_str = _UU("LH_AUTH_OPENVPN_CERT");
+					break;
 				}
 				IPToStr(ip1, sizeof(ip1), &c->FirstSock->RemoteIP);
 				IPToStr(ip2, sizeof(ip2), &c->FirstSock->LocalIP);
@@ -2128,6 +2131,50 @@ bool ServerAccept(CONNECTION *c)
 					}
 					break;
 
+				case AUTHTYPE_OPENVPN_CERT:
+					// For OpenVPN; mostly same as CLIENT_AUTHTYPE_CERT, but without
+					// signature verification, because it was already performed during TLS handshake.
+					if (c->IsInProc)
+					{
+						// Certificate authentication
+						cert_size = PackGetDataSize(p, "cert");
+						if (cert_size >= 1 && cert_size <= 100000)
+						{
+							cert_buf = ZeroMalloc(cert_size);
+							if (PackGetData(p, "cert", cert_buf))
+							{
+								BUF *b = NewBuf();
+								X *x;
+								WriteBuf(b, cert_buf, cert_size);
+								x = BufToX(b, false);
+								if (x != NULL && x->is_compatible_bit)
+								{
+									Debug("Got to SamAuthUserByCert %s\n", username); // XXX
+									// Check whether the certificate is valid.
+									auth_ret = SamAuthUserByCert(hub, username, x);
+									if (auth_ret)
+									{
+										// Copy the certificate
+										c->ClientX = CloneX(x);
+									}
+								}
+								FreeX(x);
+								FreeBuf(b);
+							}
+							Free(cert_buf);
+						}
+					}
+					else
+					{
+						// OpenVPN certificate authentication cannot be used directly by external clients
+						Unlock(hub->lock);
+						ReleaseHub(hub);
+						FreePack(p);
+						c->Err = ERR_AUTHTYPE_NOT_SUPPORTED;
+						goto CLEANUP;
+					}
+					break;
+
 				default:
 					// Unknown authentication method
 					Unlock(hub->lock);
@@ -7247,6 +7294,45 @@ PACK *PackLoginWithPlainPassword(char *hubname, char *username, void *plain_pass
 	return p;
 }
 
+// Generate a packet of OpenVPN certificate login
+PACK *PackLoginWithOpenVPNCertificate(char *hubname, char *username, X *x)
+{
+	PACK *p;
+	// Validate arguments
+	if (hubname == NULL || username == NULL || x == NULL)
+	{
+		return NULL;
+	}
+
+	p = NewPack();
+	PackAddStr(p, "method", "login");
+	PackAddStr(p, "hubname", hubname);
+
+	char cn_username[128];
+	if (IsEmptyStr(username))
+	{
+		if (x->subject_name == NULL)
+		{
+			return NULL;
+		}
+		wcstombs(cn_username, x->subject_name->CommonName, 127);
+		cn_username[127] = '\0';
+		PackAddStr(p, "username", cn_username);
+	}
+	else
+	{
+		PackAddStr(p, "username", username);
+	}
+
+	PackAddInt(p, "authtype", AUTHTYPE_OPENVPN_CERT);
+
+	BUF *cert_buf = XToBuf(x, false);
+	PackAddBuf(p, "cert", cert_buf);
+	FreeBuf(cert_buf);
+
+	return p;
+}
+
 // Create a packet of password authentication login
 PACK *PackLoginWithPassword(char *hubname, char *username, void *secure_password)
 {

+ 1 - 0
src/Cedar/Protocol.h

@@ -242,6 +242,7 @@ PACK *PackLoginWithAnonymous(char *hubname, char *username);
 PACK *PackLoginWithPassword(char *hubname, char *username, void *secure_password);
 PACK *PackLoginWithPlainPassword(char *hubname, char *username, void *plain_password);
 PACK *PackLoginWithCert(char *hubname, char *username, X *x, void *sign, UINT sign_size);
+PACK *PackLoginWithOpenVPNCertificate(char *hubname, char *username, X *x);
 bool GetMethodFromPack(PACK *p, char *method, UINT size);
 bool GetHubnameAndUsernameFromPack(PACK *p, char *username, UINT username_size,
 								   char *hubname, UINT hubname_size);

+ 8 - 0
src/Mayaqua/Encrypt.c

@@ -151,6 +151,8 @@
 
 LOCK *openssl_lock = NULL;
 
+int ssl_clientcert_index = 0;
+
 LOCK **ssl_lock_obj = NULL;
 UINT ssl_lock_num;
 static bool openssl_inited = false;
@@ -4064,6 +4066,8 @@ void InitCryptLibrary()
 	ERR_load_crypto_strings();
 	SSL_load_error_strings();
 
+	ssl_clientcert_index = SSL_get_ex_new_index(0, "struct SslClientCertInfo *", NULL, NULL, NULL);
+
 #ifdef	OS_UNIX
 	{
 		char *name1 = "/dev/random";
@@ -5289,5 +5293,9 @@ static unsigned char *Internal_SHA0(const unsigned char *d, size_t n, unsigned c
 }
 
 
+int GetSslClientCertIndex()
+{
+	return ssl_clientcert_index;
+}
 
 

+ 2 - 0
src/Mayaqua/Encrypt.h

@@ -581,6 +581,8 @@ BUF *EasyDecrypt(BUF *src_buf);
 
 void DisableIntelAesAccel();
 
+int GetSslClientCertIndex();
+
 #ifdef	ENCRYPT_C
 // Inner function
 

+ 47 - 4
src/Mayaqua/Network.c

@@ -5809,14 +5809,52 @@ SOCK *ListenAnyPortEx2(bool local_only, bool disable_ca)
 	return NULL;
 }
 
-int cb_test(int a, X509_STORE_CTX *ctx)
+// Verify client SSL certificate during TLS handshake.
+//
+// (actually, only save the certificate for later authentication in Protocol.c)
+int SslCertVerifyCallback(int preverify_ok, X509_STORE_CTX *ctx)
 {
-	WHERE;
-	return 1;
+	SSL *ssl;
+	struct SslClientCertInfo *clientcert;
+
+	ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+	clientcert = SSL_get_ex_data(ssl, GetSslClientCertIndex());
+
+	if (clientcert != NULL)
+	{
+		clientcert->PreverifyErr = 0;
+		clientcert->PreverifyErrMessage[0] = '\0';
+		if (!preverify_ok)
+		{
+			clientcert->PreverifyErr = X509_STORE_CTX_get_error(ctx);
+			const char *msg = X509_verify_cert_error_string(clientcert->PreverifyErr);
+			StrCpy(clientcert->PreverifyErrMessage, PREVERIFY_ERR_MESSAGE_SIZE, msg);
+			Debug("SslCertVerifyCallback preverify error: '%s'\n", msg);
+		}
+		else
+		{
+			if (ctx->cert != NULL)
+			{
+				X *tmpX = X509ToX(ctx->cert); // this only wraps ctx->cert, but we need to make a copy
+				X *copyX = CloneX(tmpX);
+				tmpX->do_not_free = true; // do not release inner X509 object
+				FreeX(tmpX);
+				clientcert->X = copyX;
+			}
+		}
+	}
+
+	return 1; /* allow the verification process to continue */
 }
 
 // Create a new SSL pipe
 SSL_PIPE *NewSslPipe(bool server_mode, X *x, K *k, DH_CTX *dh)
+{
+	return NewSslPipeEx(server_mode, x, k, dh, false, NULL);
+}
+
+// Create a new SSL pipe with extended options
+SSL_PIPE *NewSslPipeEx(bool server_mode, X *x, K *k, DH_CTX *dh, bool verify_peer, struct SslClientCertInfo *clientcert)
 {
 	SSL_PIPE *s;
 	SSL *ssl;
@@ -5841,7 +5879,10 @@ SSL_PIPE *NewSslPipe(bool server_mode, X *x, K *k, DH_CTX *dh)
 			SSL_CTX_set_ssl_version(ssl_ctx, SSLv23_client_method());
 		}
 
-		//SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, cb_test);
+		if (verify_peer)
+		{
+			SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, SslCertVerifyCallback);
+		}
 
 		if (dh != NULL)
 		{
@@ -5854,6 +5895,8 @@ SSL_PIPE *NewSslPipe(bool server_mode, X *x, K *k, DH_CTX *dh)
 		}
 
 		ssl = SSL_new(ssl_ctx);
+
+		SSL_set_ex_data(ssl, GetSslClientCertIndex(), clientcert);
 	}
 	Unlock(openssl_lock);
 

+ 9 - 0
src/Mayaqua/Network.h

@@ -1615,7 +1615,16 @@ void Win32WaitForTubes(TUBE **tubes, UINT num, UINT timeout);
 void UnixWaitForTubes(TUBE **tubes, UINT num, UINT timeout);
 #endif	// OS_WIN32
 
+#define PREVERIFY_ERR_MESSAGE_SIZE 100
+// Info on client certificate collected during TLS handshake
+struct SslClientCertInfo {
+	int PreverifyErr;
+	char PreverifyErrMessage[PREVERIFY_ERR_MESSAGE_SIZE];
+	X *X;
+};
+
 SSL_PIPE *NewSslPipe(bool server_mode, X *x, K *k, DH_CTX *dh);
+SSL_PIPE *NewSslPipeEx(bool server_mode, X *x, K *k, DH_CTX *dh, bool verify_peer, struct SslClientCertInfo *clientcert);
 void FreeSslPipe(SSL_PIPE *s);
 bool SyncSslPipe(SSL_PIPE *s);
 

+ 4 - 0
src/bin/hamcore/strtable_cn.stb

@@ -1810,6 +1810,9 @@ LO_PREFIX_CHANNEL		OpenVPN 会话%u (%r:%u -> %r:%u) 通道 %u:
 LO_NEW_CHANNEL			已创创建一个新通道。
 LO_CHANNEL_ESTABLISHED_NEWKEY	通道已建立。(触发器: Re-key完成。)
 LO_OPTION_STR_RECV		接收到的选项字符串:"%S"
+LO_CLIENT_CERT			Client certificate received (subject: CN="%s"), will use certificate authentication.
+LO_CLIENT_UNVERIFIED_CERT		Client certificate was provided but did not pass verification (error="%S"), will use password authentication.
+LO_CLIENT_NO_CERT		Client certificate is not provided, will use password authentication.
 LO_OPTION_STR_SEND		发送选项字符串:"%S"
 LO_NEW_SESSION			已创建新的会话。协议:%S
 LO_INITIATE_REKEY			re-keying 进程已开始。
@@ -1906,6 +1909,7 @@ LH_AUTH_PASSWORD			密码验证
 LH_AUTH_PLAIN_PASSWORD		外部服务器身份验证
 LH_AUTH_CERT				证书验证
 LH_AUTH_TICKET				票证验证
+LH_AUTH_OPENVPN_CERT    OpenVPN certificate authentication
 LH_AUTH_RADIUS_NOT_SUPPORT	连接 "%S": 用户 "%S" 身份验证方法 RADIUS 或 Active Directory (NT 域),但 VPN Server 是 "%S",因为 RADIUS 或 Active Directory (NT 域)不能使用。连接被拒绝。
 LH_AUTH_RADIUS_NOT_SUPPORT_ON_OPEN_SOURCE	"%S" 的连接方法: 用户 "%S" 的身份验证方法被指定为 RADIUS 身份验证或 Active Directory 身份验证 (NT 域验证)。然而,这样一个外部用户身份验证功能尚未在 SoftEther VPN 的开源版本上实施。该连接将被拒绝。
 LH_AUTH_CERT_NOT_SUPPORT_ON_OPEN_SOURCE	"%S" 的连接方法: 用户 "%S" 的身份验证方法被指定为证书认证。然而,证书验证功能尚未在 SoftEther VPN 的开源版本上实施。该连接将被拒绝。

+ 4 - 2
src/bin/hamcore/strtable_en.stb

@@ -1792,6 +1792,9 @@ LO_PREFIX_CHANNEL		OpenVPN Session %u (%r:%u -> %r:%u) Channel %u:
 LO_NEW_CHANNEL			A new channel is created.
 LO_CHANNEL_ESTABLISHED_NEWKEY	The channel is established. (Trigger: Re-key completion.)
 LO_OPTION_STR_RECV		Option Strings Received: "%S"
+LO_CLIENT_CERT			Client certificate received (subject: CN="%s"), will use certificate authentication.
+LO_CLIENT_UNVERIFIED_CERT		Client certificate was provided but did not pass verification (error="%S"), will use password authentication.
+LO_CLIENT_NO_CERT		Client certificate is not provided, will use password authentication.
 LO_OPTION_STR_SEND		Option Strings to Send: "%S"
 LO_NEW_SESSION			A new session is created. Protocol: %S
 LO_INITIATE_REKEY		The re-keying process is started.
@@ -1888,6 +1891,7 @@ LH_AUTH_PASSWORD		Password authentication
 LH_AUTH_PLAIN_PASSWORD	External server authentication
 LH_AUTH_CERT			Certificate authentication
 LH_AUTH_TICKET			Ticket authentication
+LH_AUTH_OPENVPN_CERT    OpenVPN certificate authentication
 LH_AUTH_RADIUS_NOT_SUPPORT	Connection "%S": The authentication method of the user "%S" has been specified as RADIUS Authentication or Active Directory Authentication (NT Domain Authentication). However, the edition of the VPN Server is "%S". This edition does not support RADIUS Authentication nor Active Directory Authentication (NT Domain Authentication). The connection will be denied.
 LH_AUTH_RADIUS_NOT_SUPPORT_ON_OPEN_SOURCE	Connection "%S": The authentication method of the user "%S" has been specified as RADIUS Authentication or Active Directory Authentication (NT Domain Authentication). However, such an external user-authentication function hasn't been implemented on the Open-Source version of SoftEther VPN yet. The connection will be denied.
 LH_AUTH_CERT_NOT_SUPPORT_ON_OPEN_SOURCE	Connection "%S": The authentication method of the user "%S" has been specified as Certificate Authentication. However, the Certificate Authentication function hasn't been implemented on the Open-Source version of SoftEther VPN yet. The connection will be denied.
@@ -7164,5 +7168,3 @@ SW_LINK_NAME_LANGUAGE_COMMENT				Change the display language setting of %s.
 
 SW_LINK_NAME_DEBUG							Debugging Information Collecting Tool
 SW_LINK_NAME_DEBUG_COMMENT					Collects debugging information of SoftEther VPN. Use this tool only if your support staff asks you to do so.
-
-

+ 4 - 0
src/bin/hamcore/strtable_ja.stb

@@ -1796,6 +1796,9 @@ LO_PREFIX_CHANNEL		OpenVPN セッション %u (%r:%u -> %r:%u) チャネル %u:
 LO_NEW_CHANNEL			新しいチャネルを作成しました。
 LO_CHANNEL_ESTABLISHED_NEWKEY	チャネルが確立状態になりました (原因: リキーの完了)。
 LO_OPTION_STR_RECV		受信したオプション文字列: "%S"
+LO_CLIENT_CERT			Client certificate received (subject: CN="%s"), will use certificate authentication.
+LO_CLIENT_UNVERIFIED_CERT		Client certificate was provided but did not pass verification (error="%S"), will use password authentication.
+LO_CLIENT_NO_CERT		Client certificate is not provided, will use password authentication.
 LO_OPTION_STR_SEND		送信するオプション文字列: "%S"
 LO_NEW_SESSION			新しいセッションを作成しました。プロトコル: %S
 LO_INITIATE_REKEY		このチャネルのリキーを開始します。
@@ -1892,6 +1895,7 @@ LH_AUTH_PASSWORD		パスワード認証
 LH_AUTH_PLAIN_PASSWORD	外部サーバー認証
 LH_AUTH_CERT			証明書認証
 LH_AUTH_TICKET			チケット認証
+LH_AUTH_OPENVPN_CERT    OpenVPN certificate authentication
 LH_AUTH_RADIUS_NOT_SUPPORT	コネクション "%S": ユーザー "%S" の認証方法として RADIUS 認証または Active Directory 認証 (NT ドメイン認証) が指定されましたが、現在の VPN Server のエディションは "%S" であるため、RADIUS 認証または Active Directory 認証 (NT ドメイン認証) を使用することができません。接続は拒否されます。
 LH_AUTH_RADIUS_NOT_SUPPORT_ON_OPEN_SOURCE	コネクション "%S": ユーザー "%S" の認証方法として RADIUS 認証または Active Directory 認証 (NT ドメイン認証) が指定されましたが、RADIUS 認証または Active Directory 認証 (NT ドメイン認証) を使用することができません。この機能はオープンソース版 SoftEther VPN にはまだ実装されていません。接続は拒否されます。
 LH_AUTH_CERT_NOT_SUPPORT_ON_OPEN_SOURCE	コネクション "%S": ユーザー "%S" の認証方法として証明書認証が指定されましたが、証明書認証を使用することができません。この機能はオープンソース版 SoftEther VPN にはまだ実装されていません。接続は拒否されます。

+ 4 - 0
src/bin/hamcore/strtable_tw.stb

@@ -1812,6 +1812,9 @@ LO_PREFIX_CHANNEL		OpenVPN 會話%u (%r:%u -> %r:%u) 通道 %u:
 LO_NEW_CHANNEL			已創建一個新通道。
 LO_CHANNEL_ESTABLISHED_NEWKEY	通道已建立。(觸發器: Re-key完成。)
 LO_OPTION_STR_RECV		接收到的選項字串:"%S"
+LO_CLIENT_CERT			Client certificate received (subject: CN="%s"), will use certificate authentication.
+LO_CLIENT_UNVERIFIED_CERT		Client certificate was provided but did not pass verification (error="%S"), will use password authentication.
+LO_CLIENT_NO_CERT		Client certificate is not provided, will use password authentication.
 LO_OPTION_STR_SEND		發送選項字串:"%S"
 LO_NEW_SESSION			已創建新的會話。協議:%S
 LO_INITIATE_REKEY			re-keying 進程已開始。
@@ -1908,6 +1911,7 @@ LH_AUTH_PASSWORD		密碼驗證
 LH_AUTH_PLAIN_PASSWORD	外部伺服器身份驗證
 LH_AUTH_CERT			證書驗證
 LH_AUTH_TICKET			票證驗證
+LH_AUTH_OPENVPN_CERT			OpenVPN certificate authentication
 LH_AUTH_RADIUS_NOT_SUPPORT	連接 "%S": 用戶 "%S" 身份驗證方法 RADIUS 或 Active Directory (NT 域),但 VPN Server 是 "%S",因為 RADIUS 或 Active Directory (NT 域)不能使用。連接被拒絕。
 LH_AUTH_RADIUS_NOT_SUPPORT_ON_OPEN_SOURCE	"%S" 的連接方法: 用戶 "%S" 的身份驗證方法被指定為 RADIUS 身份驗證或 Active Directory 身份驗證 (NT 域驗證)。然而,這樣一個外部用戶身份驗證功能尚未在 SoftEther VPN 的開源版本上實施。該連接將被拒絕。
 LH_AUTH_CERT_NOT_SUPPORT_ON_OPEN_SOURCE	"%S" 的連接方法: 用戶 "%S" 的身份驗證方法被指定為證書認證。然而,證書驗證功能尚未在 SoftEther VPN 的開源版本上實施。該連接將被拒絕。