Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 45 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,50 @@ Users can quickly get started by referring to the use cases under the Apache-IoT

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

## TLS and mTLS

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.

| keytool artifact | C# client usage |
| --- | --- |
| `ca.crt` | Pass to `SetRootCertificatePath` / `RootCertificatePath` to trust the server certificate |
| `client.keystore` | Contains the client private key and certificate chain; JDK 17 creates PKCS#12 by default, so pass it directly to `SetClientCertificatePath` |
| `client.truststore` | Java client truststore; the C# client uses `ca.crt` instead |
| `server.truststore` | Server-side truststore for trusting client certificates; not a C# client option |

Only convert the keystore first if you are reusing an older JKS file, or if it was explicitly generated with `-storetype JKS`:

```bash
$KT -importkeystore \
-srckeystore client.keystore \
-srcstorepass $PWD \
-srcalias client \
-destkeystore client.p12 \
-deststoretype PKCS12 \
-deststorepass $PWD \
-destkeypass $PWD \
-destalias client
```

C# builder example:

```csharp
var sessionPool = new SessionPool.Builder()
.SetHost("127.0.0.1")
.SetPort(6667)
.SetUseSsl(true)
.SetRootCertificatePath("tls-certs/ca.crt")
.SetClientCertificatePath("tls-certs/client.keystore")
.SetClientCertificatePassword("IoTDB")
.Build();
```

The ADO.NET connection string supports the same options:

```text
DataSource=127.0.0.1;Port=6667;UseSsl=True;RootCertificatePath=tls-certs/ca.crt;ClientCertificatePath=tls-certs/client.keystore;ClientCertificatePassword=IoTDB
```

## Developer environment requirements for iotdb-client-csharp

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

## Publish your own client on nuget.org
You can find out how to publish from this [doc](./PUBLISH.md).
You can find out how to publish from this [doc](./PUBLISH.md).
45 changes: 44 additions & 1 deletion README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,49 @@ dotnet add package Apache.IoTDB

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

## TLS 和 mTLS

通过 `SetUseSsl(true)` 开启 TLS。C# 客户端使用 .NET 的证书模型,不直接读取 Java truststore;如果证书按 JDK 17 的 Java/keytool 文档生成,`client.keystore` 默认就是 PKCS#12,可以直接作为客户端证书文件使用,并直接使用 `ca.crt` 作为信任根。

| keytool 产物 | C# 客户端用法 |
| --- | --- |
| `ca.crt` | 传给 `SetRootCertificatePath` / `RootCertificatePath`,用于信任服务端证书 |
| `client.keystore` | 包含客户端私钥和证书链;JDK 17 默认是 PKCS#12,直接传给 `SetClientCertificatePath` |
| `client.truststore` | Java 客户端的 truststore;C# 侧用 `ca.crt`,不需要这个文件 |
| `server.truststore` | 服务端用于信任客户端证书,不是 C# 客户端参数 |

只有在复用旧版 JDK 生成的 JKS 文件,或显式使用 `-storetype JKS` 生成 keystore 时,才需要先转换为 PKCS#12:

```bash
$KT -importkeystore \
-srckeystore client.keystore \
-srcstorepass $PWD \
-srcalias client \
-destkeystore client.p12 \
-deststoretype PKCS12 \
-deststorepass $PWD \
-destkeypass $PWD \
-destalias client
```

C# builder 示例:

```csharp
var sessionPool = new SessionPool.Builder()
.SetHost("127.0.0.1")
.SetPort(6667)
.SetUseSsl(true)
.SetRootCertificatePath("tls-certs/ca.crt")
.SetClientCertificatePath("tls-certs/client.keystore")
.SetClientCertificatePassword("IoTDB")
.Build();
```

ADO.NET 连接字符串也支持相同配置:

```text
DataSource=127.0.0.1;Port=6667;UseSsl=True;RootCertificatePath=tls-certs/ca.crt;ClientCertificatePath=tls-certs/client.keystore;ClientCertificatePassword=IoTDB
```

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

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

## 在 nuget.org 上发布你自己的客户端
你可以在这个[文档](./PUBLISH.md)中找到如何发布
你可以在这个[文档](./PUBLISH.md)中找到如何发布
16 changes: 15 additions & 1 deletion src/Apache.IoTDB.Data/DataReaderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,21 @@ public static class DataReaderExtensions
{
public static SessionPool CreateSession(this IoTDBConnectionStringBuilder db)
{
return new SessionPool(db.DataSource, db.Port, db.Username, db.Password, db.FetchSize, db.ZoneId, db.PoolSize, db.Compression, db.TimeOut);
return new SessionPool.Builder()
.SetHost(db.DataSource)
.SetPort(db.Port)
.SetUsername(db.Username)
.SetPassword(db.Password)
.SetFetchSize(db.FetchSize)
.SetZoneId(db.ZoneId)
.SetPoolSize(db.PoolSize)
.SetEnableRpcCompression(db.Compression)
.SetConnectionTimeoutInMs(db.TimeOut)
.SetUseSsl(db.UseSsl)
.SetClientCertificatePath(db.ClientCertificatePath)
.SetClientCertificatePassword(db.ClientCertificatePassword)
.SetRootCertificatePath(db.RootCertificatePath)
.Build();
}

public static List<T> ToObject<T>(this IDataReader dataReader)
Expand Down
86 changes: 81 additions & 5 deletions src/Apache.IoTDB.Data/IoTDBConnectionStringBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ public class IoTDBConnectionStringBuilder : DbConnectionStringBuilder
private const string PoolSizeKeyword = "PoolSize";
private const string ZoneIdKeyword = "ZoneId";
private const string TimeOutKeyword = "TimeOut";
private const string UseSslKeyword = "UseSsl";
private const string ClientCertificatePathKeyword = "ClientCertificatePath";
private const string ClientCertificatePasswordKeyword = "ClientCertificatePassword";
private const string RootCertificatePathKeyword = "RootCertificatePath";

private enum Keywords
{
Expand All @@ -55,7 +59,11 @@ private enum Keywords
Compression,
PoolSize,
ZoneId,
TimeOut
TimeOut,
UseSsl,
ClientCertificatePath,
ClientCertificatePassword,
RootCertificatePath
}

private static readonly IReadOnlyList<string> _validKeywords;
Expand All @@ -70,10 +78,14 @@ private enum Keywords
private int _port = 6667;
private int _poolSize = 8;
private int _timeOut = 10000;
private bool _useSsl = false;
private string _clientCertificatePath = null;
private string _clientCertificatePassword = null;
private string _rootCertificatePath = null;

static IoTDBConnectionStringBuilder()
{
var validKeywords = new string[9];
var validKeywords = new string[13];
validKeywords[(int)Keywords.DataSource] = DataSourceKeyword;
validKeywords[(int)Keywords.Username] = UserNameKeyword;
validKeywords[(int)Keywords.Password] = PasswordKeyword;
Expand All @@ -83,9 +95,13 @@ static IoTDBConnectionStringBuilder()
validKeywords[(int)Keywords.PoolSize] = PoolSizeKeyword;
validKeywords[(int)Keywords.ZoneId] = ZoneIdKeyword;
validKeywords[(int)Keywords.TimeOut] = TimeOutKeyword;
validKeywords[(int)Keywords.UseSsl] = UseSslKeyword;
validKeywords[(int)Keywords.ClientCertificatePath] = ClientCertificatePathKeyword;
validKeywords[(int)Keywords.ClientCertificatePassword] = ClientCertificatePasswordKeyword;
validKeywords[(int)Keywords.RootCertificatePath] = RootCertificatePathKeyword;
_validKeywords = validKeywords;

_keywords = new Dictionary<string, Keywords>(9, StringComparer.OrdinalIgnoreCase)
_keywords = new Dictionary<string, Keywords>(13, StringComparer.OrdinalIgnoreCase)
{
[DataSourceKeyword] = Keywords.DataSource,
[UserNameKeyword] = Keywords.Username,
Expand All @@ -95,7 +111,11 @@ static IoTDBConnectionStringBuilder()
[CompressionKeyword] = Keywords.Compression,
[PoolSizeKeyword] = Keywords.PoolSize,
[ZoneIdKeyword] = Keywords.ZoneId,
[TimeOutKeyword] = Keywords.TimeOut
[TimeOutKeyword] = Keywords.TimeOut,
[UseSslKeyword] = Keywords.UseSsl,
[ClientCertificatePathKeyword] = Keywords.ClientCertificatePath,
[ClientCertificatePasswordKeyword] = Keywords.ClientCertificatePassword,
[RootCertificatePathKeyword] = Keywords.RootCertificatePath
};
}

Expand Down Expand Up @@ -165,7 +185,31 @@ public virtual string ZoneId
public virtual int TimeOut
{
get => _timeOut;
set => base[PoolSizeKeyword] = _timeOut = value;
set => base[TimeOutKeyword] = _timeOut = value;
}

public virtual bool UseSsl
{
get => _useSsl;
set => base[UseSslKeyword] = _useSsl = value;
}

public virtual string ClientCertificatePath
{
get => _clientCertificatePath;
set => base[ClientCertificatePathKeyword] = _clientCertificatePath = value;
}

public virtual string ClientCertificatePassword
{
get => _clientCertificatePassword;
set => base[ClientCertificatePasswordKeyword] = _clientCertificatePassword = value;
}

public virtual string RootCertificatePath
{
get => _rootCertificatePath;
set => base[RootCertificatePathKeyword] = _rootCertificatePath = value;
}

/// <summary>
Expand Down Expand Up @@ -246,6 +290,18 @@ public override object this[string keyword]
case Keywords.TimeOut:
TimeOut = Convert.ToInt32(value, CultureInfo.InvariantCulture);
return;
case Keywords.UseSsl:
UseSsl = Convert.ToBoolean(value, CultureInfo.InvariantCulture);
return;
case Keywords.ClientCertificatePath:
ClientCertificatePath = Convert.ToString(value, CultureInfo.InvariantCulture);
return;
case Keywords.ClientCertificatePassword:
ClientCertificatePassword = Convert.ToString(value, CultureInfo.InvariantCulture);
return;
case Keywords.RootCertificatePath:
RootCertificatePath = Convert.ToString(value, CultureInfo.InvariantCulture);
return;
default:
Debug.WriteLine(false, "Unexpected keyword: " + keyword);
return;
Expand Down Expand Up @@ -376,6 +432,14 @@ private object GetAt(Keywords index)
return ZoneId;
case Keywords.TimeOut:
return TimeOut;
case Keywords.UseSsl:
return UseSsl;
case Keywords.ClientCertificatePath:
return ClientCertificatePath;
case Keywords.ClientCertificatePassword:
return ClientCertificatePassword;
case Keywords.RootCertificatePath:
return RootCertificatePath;
default:
Debug.Assert(false, "Unexpected keyword: " + index);
return null;
Expand Down Expand Up @@ -418,6 +482,18 @@ private void Reset(Keywords index)
case Keywords.TimeOut:
_timeOut = 10000;//10sec.
return;
case Keywords.UseSsl:
_useSsl = false;
return;
case Keywords.ClientCertificatePath:
_clientCertificatePath = null;
return;
case Keywords.ClientCertificatePassword:
_clientCertificatePassword = null;
return;
case Keywords.RootCertificatePath:
_rootCertificatePath = null;
return;
default:
Debug.Assert(false, "Unexpected keyword: " + index);
return;
Expand Down
28 changes: 22 additions & 6 deletions src/Apache.IoTDB/SessionPool.Builder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ public class Builder
private bool _enableRpcCompression = false;
private int _connectionTimeoutInMs = 500;
private bool _useSsl = false;
private string _certificatePath = null;
private string _clientCertificatePath = null;
private string _clientCertificatePassword = null;
private string _rootCertificatePath = null;
private string _sqlDialect = IoTDBConstant.TREE_SQL_DIALECT;
private string _database = "";
private List<string> _nodeUrls = new List<string>();
Expand Down Expand Up @@ -100,9 +102,21 @@ public Builder SetUseSsl(bool useSsl)
return this;
}

public Builder SetCertificatePath(string certificatePath)
public Builder SetClientCertificatePath(string clientCertificatePath)
{
_certificatePath = certificatePath;
_clientCertificatePath = clientCertificatePath;
return this;
}

public Builder SetClientCertificatePassword(string clientCertificatePassword)
{
_clientCertificatePassword = clientCertificatePassword;
return this;
}

public Builder SetRootCertificatePath(string rootCertificatePath)
{
_rootCertificatePath = rootCertificatePath;
return this;
}

Expand Down Expand Up @@ -136,7 +150,9 @@ public Builder()
_enableRpcCompression = false;
_connectionTimeoutInMs = 500;
_useSsl = false;
_certificatePath = null;
_clientCertificatePath = null;
_clientCertificatePassword = null;
_rootCertificatePath = null;
_sqlDialect = IoTDBConstant.TREE_SQL_DIALECT;
_database = "";
}
Expand All @@ -146,9 +162,9 @@ public SessionPool Build()
// if nodeUrls is not empty, use nodeUrls to create session pool
if (_nodeUrls.Count > 0)
{
return new SessionPool(_nodeUrls, _username, _password, _fetchSize, _zoneId, _poolSize, _enableRpcCompression, _connectionTimeoutInMs, _useSsl, _certificatePath, _sqlDialect, _database);
return new SessionPool(_nodeUrls, _username, _password, _fetchSize, _zoneId, _poolSize, _enableRpcCompression, _connectionTimeoutInMs, _useSsl, _clientCertificatePath, _clientCertificatePassword, _rootCertificatePath, _sqlDialect, _database);
}
return new SessionPool(_host, _port, _username, _password, _fetchSize, _zoneId, _poolSize, _enableRpcCompression, _connectionTimeoutInMs, _useSsl, _certificatePath, _sqlDialect, _database);
return new SessionPool(_host, _port, _username, _password, _fetchSize, _zoneId, _poolSize, _enableRpcCompression, _connectionTimeoutInMs, _useSsl, _clientCertificatePath, _clientCertificatePassword, _rootCertificatePath, _sqlDialect, _database);
}
}
}
Loading
Loading