Skip to content

Commit ba2b3dc

Browse files
committed
add mTLS support
1 parent 8214361 commit ba2b3dc

9 files changed

Lines changed: 475 additions & 33 deletions

File tree

README.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,50 @@ Users can quickly get started by referring to the use cases under the Apache-IoT
6363

6464
For those who wish to delve deeper into the client's usage and explore more advanced features, the samples directory contains additional code samples.
6565

66+
## TLS and mTLS
67+
68+
Enable TLS by calling `SetUseSsl(true)`. The C# client uses the .NET certificate model and does not read Java truststores directly. If your certificates were generated with the JDK 17 Java/keytool workflow, `client.keystore` is PKCS#12 by default and can be used directly as the client certificate file; use `ca.crt` directly as the trusted root.
69+
70+
| keytool artifact | C# client usage |
71+
| --- | --- |
72+
| `ca.crt` | Pass to `SetRootCertificatePath` / `RootCertificatePath` to trust the server certificate |
73+
| `client.keystore` | Contains the client private key and certificate chain; JDK 17 creates PKCS#12 by default, so pass it directly to `SetClientCertificatePath` |
74+
| `client.truststore` | Java client truststore; the C# client uses `ca.crt` instead |
75+
| `server.truststore` | Server-side truststore for trusting client certificates; not a C# client option |
76+
77+
Only convert the keystore first if you are reusing an older JKS file, or if it was explicitly generated with `-storetype JKS`:
78+
79+
```bash
80+
$KT -importkeystore \
81+
-srckeystore client.keystore \
82+
-srcstorepass $PWD \
83+
-srcalias client \
84+
-destkeystore client.p12 \
85+
-deststoretype PKCS12 \
86+
-deststorepass $PWD \
87+
-destkeypass $PWD \
88+
-destalias client
89+
```
90+
91+
C# builder example:
92+
93+
```csharp
94+
var sessionPool = new SessionPool.Builder()
95+
.SetHost("127.0.0.1")
96+
.SetPort(6667)
97+
.SetUseSsl(true)
98+
.SetRootCertificatePath("tls-certs/ca.crt")
99+
.SetClientCertificatePath("tls-certs/client.keystore")
100+
.SetClientCertificatePassword("IoTDB")
101+
.Build();
102+
```
103+
104+
The ADO.NET connection string supports the same options:
105+
106+
```text
107+
DataSource=127.0.0.1;Port=6667;UseSsl=True;RootCertificatePath=tls-certs/ca.crt;ClientCertificatePath=tls-certs/client.keystore;ClientCertificatePassword=IoTDB
108+
```
109+
66110
## Developer environment requirements for iotdb-client-csharp
67111

68112
```
@@ -101,4 +145,4 @@ dotnet format
101145
The CI pipeline will automatically check code formatting on all pull requests. Please ensure your code is properly formatted before submitting a PR.
102146

103147
## Publish your own client on nuget.org
104-
You can find out how to publish from this [doc](./PUBLISH.md).
148+
You can find out how to publish from this [doc](./PUBLISH.md).

README_ZH.md

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,49 @@ dotnet add package Apache.IoTDB
6161

6262
对于希望深入了解客户端用法并探索更高级特性的用户,samples目录包含了额外的代码示例。
6363

64+
## TLS 和 mTLS
65+
66+
通过 `SetUseSsl(true)` 开启 TLS。C# 客户端使用 .NET 的证书模型,不直接读取 Java truststore;如果证书按 JDK 17 的 Java/keytool 文档生成,`client.keystore` 默认就是 PKCS#12,可以直接作为客户端证书文件使用,并直接使用 `ca.crt` 作为信任根。
67+
68+
| keytool 产物 | C# 客户端用法 |
69+
| --- | --- |
70+
| `ca.crt` | 传给 `SetRootCertificatePath` / `RootCertificatePath`,用于信任服务端证书 |
71+
| `client.keystore` | 包含客户端私钥和证书链;JDK 17 默认是 PKCS#12,直接传给 `SetClientCertificatePath` |
72+
| `client.truststore` | Java 客户端的 truststore;C# 侧用 `ca.crt`,不需要这个文件 |
73+
| `server.truststore` | 服务端用于信任客户端证书,不是 C# 客户端参数 |
74+
75+
只有在复用旧版 JDK 生成的 JKS 文件,或显式使用 `-storetype JKS` 生成 keystore 时,才需要先转换为 PKCS#12
76+
77+
```bash
78+
$KT -importkeystore \
79+
-srckeystore client.keystore \
80+
-srcstorepass $PWD \
81+
-srcalias client \
82+
-destkeystore client.p12 \
83+
-deststoretype PKCS12 \
84+
-deststorepass $PWD \
85+
-destkeypass $PWD \
86+
-destalias client
87+
```
88+
89+
C# builder 示例:
90+
91+
```csharp
92+
var sessionPool = new SessionPool.Builder()
93+
.SetHost("127.0.0.1")
94+
.SetPort(6667)
95+
.SetUseSsl(true)
96+
.SetRootCertificatePath("tls-certs/ca.crt")
97+
.SetClientCertificatePath("tls-certs/client.keystore")
98+
.SetClientCertificatePassword("IoTDB")
99+
.Build();
100+
```
101+
102+
ADO.NET 连接字符串也支持相同配置:
103+
104+
```text
105+
DataSource=127.0.0.1;Port=6667;UseSsl=True;RootCertificatePath=tls-certs/ca.crt;ClientCertificatePath=tls-certs/client.keystore;ClientCertificatePassword=IoTDB
106+
```
64107

65108
## iotdb-client-csharp的开发者环境要求
66109

@@ -100,4 +143,4 @@ dotnet format
100143
CI 流水线会在所有 Pull Request 上自动检查代码格式。请确保在提交 PR 之前代码格式正确。
101144

102145
## 在 nuget.org 上发布你自己的客户端
103-
你可以在这个[文档](./PUBLISH.md)中找到如何发布
146+
你可以在这个[文档](./PUBLISH.md)中找到如何发布

src/Apache.IoTDB.Data/DataReaderExtensions.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,21 @@ public static class DataReaderExtensions
3232
{
3333
public static SessionPool CreateSession(this IoTDBConnectionStringBuilder db)
3434
{
35-
return new SessionPool(db.DataSource, db.Port, db.Username, db.Password, db.FetchSize, db.ZoneId, db.PoolSize, db.Compression, db.TimeOut);
35+
return new SessionPool.Builder()
36+
.SetHost(db.DataSource)
37+
.SetPort(db.Port)
38+
.SetUsername(db.Username)
39+
.SetPassword(db.Password)
40+
.SetFetchSize(db.FetchSize)
41+
.SetZoneId(db.ZoneId)
42+
.SetPoolSize(db.PoolSize)
43+
.SetEnableRpcCompression(db.Compression)
44+
.SetConnectionTimeoutInMs(db.TimeOut)
45+
.SetUseSsl(db.UseSsl)
46+
.SetClientCertificatePath(db.ClientCertificatePath)
47+
.SetClientCertificatePassword(db.ClientCertificatePassword)
48+
.SetRootCertificatePath(db.RootCertificatePath)
49+
.Build();
3650
}
3751

3852
public static List<T> ToObject<T>(this IDataReader dataReader)

src/Apache.IoTDB.Data/IoTDBConnectionStringBuilder.cs

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ public class IoTDBConnectionStringBuilder : DbConnectionStringBuilder
4444
private const string PoolSizeKeyword = "PoolSize";
4545
private const string ZoneIdKeyword = "ZoneId";
4646
private const string TimeOutKeyword = "TimeOut";
47+
private const string UseSslKeyword = "UseSsl";
48+
private const string ClientCertificatePathKeyword = "ClientCertificatePath";
49+
private const string ClientCertificatePasswordKeyword = "ClientCertificatePassword";
50+
private const string RootCertificatePathKeyword = "RootCertificatePath";
4751

4852
private enum Keywords
4953
{
@@ -55,7 +59,11 @@ private enum Keywords
5559
Compression,
5660
PoolSize,
5761
ZoneId,
58-
TimeOut
62+
TimeOut,
63+
UseSsl,
64+
ClientCertificatePath,
65+
ClientCertificatePassword,
66+
RootCertificatePath
5967
}
6068

6169
private static readonly IReadOnlyList<string> _validKeywords;
@@ -70,10 +78,14 @@ private enum Keywords
7078
private int _port = 6667;
7179
private int _poolSize = 8;
7280
private int _timeOut = 10000;
81+
private bool _useSsl = false;
82+
private string _clientCertificatePath = null;
83+
private string _clientCertificatePassword = null;
84+
private string _rootCertificatePath = null;
7385

7486
static IoTDBConnectionStringBuilder()
7587
{
76-
var validKeywords = new string[9];
88+
var validKeywords = new string[13];
7789
validKeywords[(int)Keywords.DataSource] = DataSourceKeyword;
7890
validKeywords[(int)Keywords.Username] = UserNameKeyword;
7991
validKeywords[(int)Keywords.Password] = PasswordKeyword;
@@ -83,9 +95,13 @@ static IoTDBConnectionStringBuilder()
8395
validKeywords[(int)Keywords.PoolSize] = PoolSizeKeyword;
8496
validKeywords[(int)Keywords.ZoneId] = ZoneIdKeyword;
8597
validKeywords[(int)Keywords.TimeOut] = TimeOutKeyword;
98+
validKeywords[(int)Keywords.UseSsl] = UseSslKeyword;
99+
validKeywords[(int)Keywords.ClientCertificatePath] = ClientCertificatePathKeyword;
100+
validKeywords[(int)Keywords.ClientCertificatePassword] = ClientCertificatePasswordKeyword;
101+
validKeywords[(int)Keywords.RootCertificatePath] = RootCertificatePathKeyword;
86102
_validKeywords = validKeywords;
87103

88-
_keywords = new Dictionary<string, Keywords>(9, StringComparer.OrdinalIgnoreCase)
104+
_keywords = new Dictionary<string, Keywords>(13, StringComparer.OrdinalIgnoreCase)
89105
{
90106
[DataSourceKeyword] = Keywords.DataSource,
91107
[UserNameKeyword] = Keywords.Username,
@@ -95,7 +111,11 @@ static IoTDBConnectionStringBuilder()
95111
[CompressionKeyword] = Keywords.Compression,
96112
[PoolSizeKeyword] = Keywords.PoolSize,
97113
[ZoneIdKeyword] = Keywords.ZoneId,
98-
[TimeOutKeyword] = Keywords.TimeOut
114+
[TimeOutKeyword] = Keywords.TimeOut,
115+
[UseSslKeyword] = Keywords.UseSsl,
116+
[ClientCertificatePathKeyword] = Keywords.ClientCertificatePath,
117+
[ClientCertificatePasswordKeyword] = Keywords.ClientCertificatePassword,
118+
[RootCertificatePathKeyword] = Keywords.RootCertificatePath
99119
};
100120
}
101121

@@ -165,7 +185,31 @@ public virtual string ZoneId
165185
public virtual int TimeOut
166186
{
167187
get => _timeOut;
168-
set => base[PoolSizeKeyword] = _timeOut = value;
188+
set => base[TimeOutKeyword] = _timeOut = value;
189+
}
190+
191+
public virtual bool UseSsl
192+
{
193+
get => _useSsl;
194+
set => base[UseSslKeyword] = _useSsl = value;
195+
}
196+
197+
public virtual string ClientCertificatePath
198+
{
199+
get => _clientCertificatePath;
200+
set => base[ClientCertificatePathKeyword] = _clientCertificatePath = value;
201+
}
202+
203+
public virtual string ClientCertificatePassword
204+
{
205+
get => _clientCertificatePassword;
206+
set => base[ClientCertificatePasswordKeyword] = _clientCertificatePassword = value;
207+
}
208+
209+
public virtual string RootCertificatePath
210+
{
211+
get => _rootCertificatePath;
212+
set => base[RootCertificatePathKeyword] = _rootCertificatePath = value;
169213
}
170214

171215
/// <summary>
@@ -246,6 +290,18 @@ public override object this[string keyword]
246290
case Keywords.TimeOut:
247291
TimeOut = Convert.ToInt32(value, CultureInfo.InvariantCulture);
248292
return;
293+
case Keywords.UseSsl:
294+
UseSsl = Convert.ToBoolean(value, CultureInfo.InvariantCulture);
295+
return;
296+
case Keywords.ClientCertificatePath:
297+
ClientCertificatePath = Convert.ToString(value, CultureInfo.InvariantCulture);
298+
return;
299+
case Keywords.ClientCertificatePassword:
300+
ClientCertificatePassword = Convert.ToString(value, CultureInfo.InvariantCulture);
301+
return;
302+
case Keywords.RootCertificatePath:
303+
RootCertificatePath = Convert.ToString(value, CultureInfo.InvariantCulture);
304+
return;
249305
default:
250306
Debug.WriteLine(false, "Unexpected keyword: " + keyword);
251307
return;
@@ -376,6 +432,14 @@ private object GetAt(Keywords index)
376432
return ZoneId;
377433
case Keywords.TimeOut:
378434
return TimeOut;
435+
case Keywords.UseSsl:
436+
return UseSsl;
437+
case Keywords.ClientCertificatePath:
438+
return ClientCertificatePath;
439+
case Keywords.ClientCertificatePassword:
440+
return ClientCertificatePassword;
441+
case Keywords.RootCertificatePath:
442+
return RootCertificatePath;
379443
default:
380444
Debug.Assert(false, "Unexpected keyword: " + index);
381445
return null;
@@ -418,6 +482,18 @@ private void Reset(Keywords index)
418482
case Keywords.TimeOut:
419483
_timeOut = 10000;//10sec.
420484
return;
485+
case Keywords.UseSsl:
486+
_useSsl = false;
487+
return;
488+
case Keywords.ClientCertificatePath:
489+
_clientCertificatePath = null;
490+
return;
491+
case Keywords.ClientCertificatePassword:
492+
_clientCertificatePassword = null;
493+
return;
494+
case Keywords.RootCertificatePath:
495+
_rootCertificatePath = null;
496+
return;
421497
default:
422498
Debug.Assert(false, "Unexpected keyword: " + index);
423499
return;

src/Apache.IoTDB/SessionPool.Builder.cs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ public class Builder
3535
private bool _enableRpcCompression = false;
3636
private int _connectionTimeoutInMs = 500;
3737
private bool _useSsl = false;
38-
private string _certificatePath = null;
38+
private string _clientCertificatePath = null;
39+
private string _clientCertificatePassword = null;
40+
private string _rootCertificatePath = null;
3941
private string _sqlDialect = IoTDBConstant.TREE_SQL_DIALECT;
4042
private string _database = "";
4143
private List<string> _nodeUrls = new List<string>();
@@ -100,9 +102,21 @@ public Builder SetUseSsl(bool useSsl)
100102
return this;
101103
}
102104

103-
public Builder SetCertificatePath(string certificatePath)
105+
public Builder SetClientCertificatePath(string clientCertificatePath)
104106
{
105-
_certificatePath = certificatePath;
107+
_clientCertificatePath = clientCertificatePath;
108+
return this;
109+
}
110+
111+
public Builder SetClientCertificatePassword(string clientCertificatePassword)
112+
{
113+
_clientCertificatePassword = clientCertificatePassword;
114+
return this;
115+
}
116+
117+
public Builder SetRootCertificatePath(string rootCertificatePath)
118+
{
119+
_rootCertificatePath = rootCertificatePath;
106120
return this;
107121
}
108122

@@ -136,7 +150,9 @@ public Builder()
136150
_enableRpcCompression = false;
137151
_connectionTimeoutInMs = 500;
138152
_useSsl = false;
139-
_certificatePath = null;
153+
_clientCertificatePath = null;
154+
_clientCertificatePassword = null;
155+
_rootCertificatePath = null;
140156
_sqlDialect = IoTDBConstant.TREE_SQL_DIALECT;
141157
_database = "";
142158
}
@@ -146,9 +162,9 @@ public SessionPool Build()
146162
// if nodeUrls is not empty, use nodeUrls to create session pool
147163
if (_nodeUrls.Count > 0)
148164
{
149-
return new SessionPool(_nodeUrls, _username, _password, _fetchSize, _zoneId, _poolSize, _enableRpcCompression, _connectionTimeoutInMs, _useSsl, _certificatePath, _sqlDialect, _database);
165+
return new SessionPool(_nodeUrls, _username, _password, _fetchSize, _zoneId, _poolSize, _enableRpcCompression, _connectionTimeoutInMs, _useSsl, _clientCertificatePath, _clientCertificatePassword, _rootCertificatePath, _sqlDialect, _database);
150166
}
151-
return new SessionPool(_host, _port, _username, _password, _fetchSize, _zoneId, _poolSize, _enableRpcCompression, _connectionTimeoutInMs, _useSsl, _certificatePath, _sqlDialect, _database);
167+
return new SessionPool(_host, _port, _username, _password, _fetchSize, _zoneId, _poolSize, _enableRpcCompression, _connectionTimeoutInMs, _useSsl, _clientCertificatePath, _clientCertificatePassword, _rootCertificatePath, _sqlDialect, _database);
152168
}
153169
}
154170
}

0 commit comments

Comments
 (0)