- NET API: added TLS support for server side

pull/68/head v1.2.2
Michael Zillgith 7 years ago
parent 38ee7b43ef
commit 49d06cc9d3

@ -27,6 +27,7 @@ using System.Collections.Generic;
using System.Collections;
using IEC61850.Common;
using IEC61850.TLS;
/// <summary>
/// IEC 61850 API for the libiec61850 .NET wrapper library
@ -558,9 +559,6 @@ namespace IEC61850
/// </summary>
public class IedServer
{
[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
static extern IntPtr IedServer_create(IntPtr modelRef);
[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
static extern IntPtr IedServer_createWithConfig(IntPtr modelRef, IntPtr tlsConfiguration, IntPtr serverConfiguratio);
@ -789,19 +787,30 @@ namespace IEC61850
private Dictionary<IntPtr, ClientConnection> clientConnections = new Dictionary<IntPtr, ClientConnection> ();
public IedServer(IedModel iedModel)
public IedServer(IedModel iedModel, IedServerConfig config = null)
{
self = IedServer_create(iedModel.self);
IntPtr nativeConfig = IntPtr.Zero;
if (config != null)
nativeConfig = config.self;
self = IedServer_createWithConfig (iedModel.self, IntPtr.Zero, nativeConfig);
}
public IedServer(IedModel iedModel, IedServerConfig config)
public IedServer(IedModel iedModel, TLSConfiguration tlsConfig, IedServerConfig config = null)
{
IntPtr nativeConfig = IntPtr.Zero;
IntPtr nativeTLSConfig = IntPtr.Zero;
if (config != null)
nativeConfig = config.self;
self = IedServer_createWithConfig (iedModel.self, IntPtr.Zero, nativeConfig);
if (tlsConfig != null)
nativeTLSConfig = tlsConfig.GetNativeInstance ();
self = IedServer_createWithConfig (iedModel.self, nativeTLSConfig, nativeConfig);
}
// causes undefined behavior
@ -850,7 +859,7 @@ namespace IEC61850
/// <summary>Start MMS server</summary>
public void Start ()
{
Start(102);
Start(-1);
}
/// <summary>

@ -139,7 +139,6 @@ namespace IEC61850
public void SetOwnCertificate(string filename)
{
if (TLSConfiguration_setOwnCertificateFromFile (self, filename) == false) {
Console.WriteLine ("Failed to read certificate from file!");
throw new CryptographicException ("Failed to read certificate from file");
}
}
@ -149,7 +148,6 @@ namespace IEC61850
byte[] certBytes = cert.GetRawCertData ();
if (TLSConfiguration_setOwnCertificate (self, certBytes, certBytes.Length) == false) {
Console.WriteLine ("Failed to set certificate!");
throw new CryptographicException ("Failed to set certificate");
}
}
@ -157,7 +155,6 @@ namespace IEC61850
public void AddAllowedCertificate(string filename)
{
if (TLSConfiguration_addAllowedCertificateFromFile (self, filename) == false) {
Console.WriteLine ("Failed to read allowed certificate from file!");
throw new CryptographicException ("Failed to read allowed certificate from file");
}
}
@ -167,7 +164,6 @@ namespace IEC61850
byte[] certBytes = cert.GetRawCertData ();
if (TLSConfiguration_addAllowedCertificate (self, certBytes, certBytes.Length) == false) {
Console.WriteLine ("Failed to add allowed certificate!");
throw new CryptographicException ("Failed to add allowed certificate");
}
}
@ -175,7 +171,6 @@ namespace IEC61850
public void AddCACertificate(string filename)
{
if (TLSConfiguration_addCACertificateFromFile (self, filename) == false) {
Console.WriteLine ("Failed to read CA certificate from file!");
throw new CryptographicException ("Failed to read CA certificate from file");
}
}
@ -185,7 +180,6 @@ namespace IEC61850
byte[] certBytes = cert.GetRawCertData ();
if (TLSConfiguration_addCACertificate (self, certBytes, certBytes.Length) == false) {
Console.WriteLine ("Failed to add CA certificate!");
throw new CryptographicException ("Failed to add CA certificate");
}
}
@ -193,7 +187,6 @@ namespace IEC61850
public void SetOwnKey (string filename, string password)
{
if (TLSConfiguration_setOwnKeyFromFile (self, filename, password) == false) {
Console.WriteLine ("Failed to read own key from file!");
throw new CryptographicException ("Failed to read own key from file");
}
}
@ -203,7 +196,6 @@ namespace IEC61850
byte[] certBytes = key.Export (X509ContentType.Pkcs12);
if (TLSConfiguration_setOwnKey (self, certBytes, certBytes.Length, password) == false) {
Console.WriteLine ("Failed to set own key!");
throw new CryptographicException ("Failed to set own key");
}
}

@ -42,6 +42,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "goose_subscriber", "goose_s
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sv_subscriber", "sv_subscriber\sv_subscriber.csproj", "{44651D2D-3252-4FD5-8B8B-5552DBE1B499}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tls_server_example", "tls_server_example\tls_server_example.csproj", "{B63F7A81-1D3A-4F2F-A7C2-D6F77E5BD307}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -96,6 +98,10 @@ Global
{9E29B4CE-EE5F-4CA6-85F6-5D1FF8B27BF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9E29B4CE-EE5F-4CA6-85F6-5D1FF8B27BF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9E29B4CE-EE5F-4CA6-85F6-5D1FF8B27BF8}.Release|Any CPU.Build.0 = Release|Any CPU
{B63F7A81-1D3A-4F2F-A7C2-D6F77E5BD307}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B63F7A81-1D3A-4F2F-A7C2-D6F77E5BD307}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B63F7A81-1D3A-4F2F-A7C2-D6F77E5BD307}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B63F7A81-1D3A-4F2F-A7C2-D6F77E5BD307}.Release|Any CPU.Build.0 = Release|Any CPU
{C351CFA4-E54E-49A1-86CE-69643535541A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C351CFA4-E54E-49A1-86CE-69643535541A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C351CFA4-E54E-49A1-86CE-69643535541A}.Release|Any CPU.ActiveCfg = Release|Any CPU

@ -0,0 +1,85 @@
using System;
using System.Threading;
using System.Security.Cryptography.X509Certificates;
using IEC61850.Server;
using IEC61850.Common;
using IEC61850.TLS;
namespace tls_server_example
{
class MainClass
{
public static void Main (string[] args)
{
bool running = true;
/* run until Ctrl-C is pressed */
Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) {
e.Cancel = true;
running = false;
};
IedModel iedModel = ConfigFileParser.CreateModelFromConfigFile ("model.cfg");
if (iedModel == null) {
Console.WriteLine ("No valid data model found!");
return;
}
DataObject spcso1 = (DataObject)iedModel.GetModelNodeByShortObjectReference ("GenericIO/GGIO1.SPCSO1");
TLSConfiguration tlsConfig = new TLSConfiguration ();
tlsConfig.SetOwnCertificate (new X509Certificate2 ("server.cer"));
tlsConfig.SetOwnKey ("server-key.pem", null);
// Add a CA certificate to check the certificate provided by the server - not required when ChainValidation == false
tlsConfig.AddCACertificate (new X509Certificate2 ("root.cer"));
// Check if the certificate is signed by a provided CA
tlsConfig.ChainValidation = true;
// Check that the shown server certificate is in the list of allowed certificates
tlsConfig.AllowOnlyKnownCertificates = false;
IedServer iedServer = new IedServer (iedModel, tlsConfig);
iedServer.SetControlHandler (spcso1, delegate(DataObject controlObject, object parameter, MmsValue ctlVal, bool test) {
bool val = ctlVal.GetBoolean();
if (val)
Console.WriteLine("received binary control command: on");
else
Console.WriteLine("received binary control command: off");
return ControlHandlerResult.OK;
}, null);
iedServer.Start ();
Console.WriteLine ("Server started");
GC.Collect ();
DataObject ggio1AnIn1 = (DataObject)iedModel.GetModelNodeByShortObjectReference ("GenericIO/GGIO1.AnIn1");
DataAttribute ggio1AnIn1magF = (DataAttribute)ggio1AnIn1.GetChild ("mag.f");
DataAttribute ggio1AnIn1T = (DataAttribute)ggio1AnIn1.GetChild ("t");
float floatVal = 1.0f;
while (running) {
floatVal += 1f;
iedServer.UpdateTimestampAttributeValue (ggio1AnIn1T, new Timestamp (DateTime.Now));
iedServer.UpdateFloatAttributeValue (ggio1AnIn1magF, floatVal);
Thread.Sleep (100);
}
iedServer.Stop ();
Console.WriteLine ("Server stopped");
iedServer.Destroy ();
}
}
}

@ -0,0 +1,27 @@
using System.Reflection;
using System.Runtime.CompilerServices;
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle ("tls_server_example")]
[assembly: AssemblyDescription ("")]
[assembly: AssemblyConfiguration ("")]
[assembly: AssemblyCompany ("")]
[assembly: AssemblyProduct ("")]
[assembly: AssemblyCopyright ("mzillgit")]
[assembly: AssemblyTrademark ("")]
[assembly: AssemblyCulture ("")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion ("1.0.*")]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]

@ -0,0 +1,237 @@
MODEL(simpleIO){
LD(GenericIO){
LN(LLN0){
DO(Mod 0){
DA(stVal 0 12 0 1 0);
DA(q 0 23 0 2 0);
DA(t 0 22 0 0 0);
DA(ctlModel 0 12 4 0 0)=0;
}
DO(Beh 0){
DA(stVal 0 12 0 1 0);
DA(q 0 23 0 2 0);
DA(t 0 22 0 0 0);
}
DO(Health 0){
DA(stVal 0 3 0 1 0);
DA(q 0 23 0 2 0);
DA(t 0 22 0 0 0);
}
DO(NamPlt 0){
DA(vendor 0 20 5 0 0);
DA(swRev 0 20 5 0 0);
DA(d 0 20 5 0 0);
DA(configRev 0 20 5 0 0);
DA(ldNs 0 20 11 0 0);
}
DS(Events){
DE(GGIO1$ST$SPCSO1$stVal);
DE(GGIO1$ST$SPCSO2$stVal);
DE(GGIO1$ST$SPCSO3$stVal);
DE(GGIO1$ST$SPCSO4$stVal);
}
DS(AnalogValues){
DE(GGIO1$MX$AnIn1);
DE(GGIO1$MX$AnIn2);
DE(GGIO1$MX$AnIn3);
DE(GGIO1$MX$AnIn4);
}
RC(EventsRCB01 Events 0 Events 1 24 111 50 1000);
RC(AnalogValuesRCB01 AnalogValues 0 AnalogValues 1 24 111 50 1000);
LC(EventLog Events GenericIO/LLN0$EventLog 19 0 0 1);
LC(GeneralLog - - 19 0 0 1);
LOG(GeneralLog);
LOG(EventLog);
GC(gcbEvents events Events 2 0 -1 -1 ){
PA(4 273 4096 010ccd010001);
}
GC(gcbAnalogValues analog AnalogValues 2 0 -1 -1 ){
PA(4 273 4096 010ccd010001);
}
}
LN(LPHD1){
DO(PhyNam 0){
DA(vendor 0 20 5 0 0);
}
DO(PhyHealth 0){
DA(stVal 0 3 0 1 0);
DA(q 0 23 0 2 0);
DA(t 0 22 0 0 0);
}
DO(Proxy 0){
DA(stVal 0 0 0 1 0);
DA(q 0 23 0 2 0);
DA(t 0 22 0 0 0);
}
}
LN(GGIO1){
DO(Mod 0){
DA(stVal 0 12 0 1 0);
DA(q 0 23 0 2 0);
DA(t 0 22 0 0 0);
DA(ctlModel 0 12 4 0 0)=0;
}
DO(Beh 0){
DA(stVal 0 12 0 1 0);
DA(q 0 23 0 2 0);
DA(t 0 22 0 0 0);
}
DO(Health 0){
DA(stVal 0 3 0 1 0);
DA(q 0 23 0 2 0);
DA(t 0 22 0 0 0);
}
DO(NamPlt 0){
DA(vendor 0 20 5 0 0);
DA(swRev 0 20 5 0 0);
DA(d 0 20 5 0 0);
}
DO(AnIn1 0){
DA(mag 0 27 1 1 0){
DA(f 0 10 1 1 0);
}
DA(q 0 23 1 2 0);
DA(t 0 22 1 0 0);
}
DO(AnIn2 0){
DA(mag 0 27 1 1 101){
DA(f 0 10 1 1 0);
}
DA(q 0 23 1 2 0);
DA(t 0 22 1 0 102);
}
DO(AnIn3 0){
DA(mag 0 27 1 1 0){
DA(f 0 10 1 1 0);
}
DA(q 0 23 1 2 0);
DA(t 0 22 1 0 0);
}
DO(AnIn4 0){
DA(mag 0 27 1 1 0){
DA(f 0 10 1 1 0);
}
DA(q 0 23 1 2 0);
DA(t 0 22 1 0 0);
}
DO(SPCSO1 0){
DA(stVal 0 0 0 1 0);
DA(q 0 23 0 2 0);
DA(Oper 0 27 12 0 0){
DA(ctlVal 0 0 12 0 0);
DA(origin 0 27 12 0 0){
DA(orCat 0 12 12 0 0);
DA(orIdent 0 13 12 0 0);
}
DA(ctlNum 0 6 12 0 0);
DA(T 0 22 12 0 0);
DA(Test 0 0 12 0 0);
DA(Check 0 24 12 0 0);
}
DA(ctlModel 0 12 4 0 0)=1;
DA(t 0 22 0 0 0);
}
DO(SPCSO2 0){
DA(stVal 0 0 0 1 0);
DA(q 0 23 0 2 0);
DA(Oper 0 27 12 0 0){
DA(ctlVal 0 0 12 0 0);
DA(origin 0 27 12 0 0){
DA(orCat 0 12 12 0 0);
DA(orIdent 0 13 12 0 0);
}
DA(ctlNum 0 6 12 0 0);
DA(T 0 22 12 0 0);
DA(Test 0 0 12 0 0);
DA(Check 0 24 12 0 0);
}
DA(ctlModel 0 12 4 0 0)=1;
DA(t 0 22 0 0 0);
}
DO(SPCSO3 0){
DA(stVal 0 0 0 1 0);
DA(q 0 23 0 2 0);
DA(Oper 0 27 12 0 0){
DA(ctlVal 0 0 12 0 0);
DA(origin 0 27 12 0 0){
DA(orCat 0 12 12 0 0);
DA(orIdent 0 13 12 0 0);
}
DA(ctlNum 0 6 12 0 0);
DA(T 0 22 12 0 0);
DA(Test 0 0 12 0 0);
DA(Check 0 24 12 0 0);
}
DA(ctlModel 0 12 4 0 0)=1;
DA(t 0 22 0 0 0);
}
DO(SPCSO4 0){
DA(stVal 0 0 0 1 0);
DA(q 0 23 0 2 0);
DA(Oper 0 27 12 0 0){
DA(ctlVal 0 0 12 0 0);
DA(origin 0 27 12 0 0){
DA(orCat 0 12 12 0 0);
DA(orIdent 0 13 12 0 0);
}
DA(ctlNum 0 6 12 0 0);
DA(T 0 22 12 0 0);
DA(Test 0 0 12 0 0);
DA(Check 0 24 12 0 0);
}
DA(ctlModel 0 12 4 0 0)=1;
DA(t 0 22 0 0 0);
}
DO(Ind1 0){
DA(stVal 0 0 0 1 0);
DA(q 0 23 0 2 0);
DA(t 0 22 0 0 0);
}
DO(Ind2 0){
DA(stVal 0 0 0 1 0);
DA(q 0 23 0 2 0);
DA(t 0 22 0 0 0);
}
DO(Ind3 0){
DA(stVal 0 0 0 1 0);
DA(q 0 23 0 2 0);
DA(t 0 22 0 0 0);
}
DO(Ind4 0){
DA(stVal 0 0 0 1 0);
DA(q 0 23 0 2 0);
DA(t 0 22 0 0 0);
}
}
LN(PDUP1){
DO(Beh 0){
DA(stVal 0 12 0 1 0);
DA(q 0 23 0 2 0);
DA(t 0 22 0 0 0);
}
DO(Mod 0){
DA(stVal 0 12 0 1 0);
DA(q 0 23 0 2 0);
DA(t 0 22 0 0 0);
DA(ctlModel 0 12 4 0 0)=0;
}
DO(Str 0){
DA(general 0 0 0 1 0);
DA(dirGeneral 0 12 0 1 0);
DA(q 0 23 0 2 0);
DA(t 0 22 0 0 0);
}
DO(Op 0){
DA(general 0 0 0 1 0);
DA(q 0 23 0 2 0);
DA(t 0 22 0 0 0);
}
DO(OpDlTmms 0){
DA(setVal 0 3 2 1 0);
}
DO(RsDlTmms 0){
DA(setVal 0 3 2 1 0);
}
}
}
}

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAu3Fjxb904UdyV/dDfvs8SFi6zJeNoMYjmpXm/LBcFSH3zGoK
wdcMovrUTED3Cc6Ww84AYpJ5MRMPTct7DfKJkWfSnkzOPmLldTSTv3RvzGVb4NzK
QqA5aSVDqAzPiP5RnFT6Q4KWRe69TMFxpw7zMXCJx9jDggqN1oojGGkmSgYGXnFd
Nc20Mujejh5pihgwnN4Y/bPFyxJwvIMj+D8qr9klhEmXKPTiX9UFd8oLkn9JCB6+
SiHhNyFIo+Llossn1Q2hxCGty36fAhEWzpfBTaY510VLjie9y4q9GPTwxITDqSQd
xcX8IuvrbxX0DyEK507SMmTJmB9448eF9ZCWFQIDAQABAoIBAC80BuQtqslwrKLq
adz4d93gOmp7X/c07pJnXZwU7ZuEylp3+e2Gsm/4qq3pTkzx8ZWtsvsf19U774av
z3VbtrkfZDLpNKcRUKeLbgmw0NawT8r4zxaoMsz/zWHsl/bv1K2B2ORXZnCGBrXl
oTFo2mWA6bGiLNn6vm1grCXhlPreywyG/kFK3pi2VvkpvG3XZSI7mmZ0Dq/MD3nO
03oOZBqwwnMObfQQdhKE7646/+KgeuF/JsXaUH4bkHmtzYWyocWYMqpC0hjpNWlQ
cKuQ7t1kfmpsGD9aNW4+ND2ok9BdxIiC+rPXS9NDqZxoWLp+a8seU++uqk1l8RPq
tPE3LqECgYEAz1NmemNLiUsKvyemUvjp8+dJupzWtdV7fsnCbYhj/5gDA2UhFKCf
dP9xiHCdNe0797oAqHY7c3JhS4ug8haDy9aDIu5GG2DNYzjX/oYm4ywbCdRx+uEN
RcTw69FjSYVGkObmxWYszwsFybRasV6PYamg65qYR3FlvW2Td4Fndy8CgYEA53L/
zHtBRQiNGJU9jfMHeX0bTtXIAt622Qn78jw0it/rhXWi2RwG2Cw5Q2aPRJ6uMt9F
yk1+GAPZcwYqwjq/nKRrl71Tn+KDWIk5rz1fNYRkaXtnMLs2MOogqoDTBshW0QBq
tnPrFNsaLKX6V92Az69wHjd2uwvLQLTvS/EuNfsCgYEAr3to/uhytAd3VirKRep3
o0E+D5zWw1upxrwhPDK4aUuSKVp8sIfvz8iyoQiomE9vdZPTIMPKOEI1BgtuM9pI
vcyYfIVvg5bg4T3o3H9SBPB9BknyG6ZHZKl4PjGht0X+X4GBDM4Z2Tj8Mijcpsph
1AkOsrzMbZQWyEoqCnnWSHMCgYAFEHUcak4BTrCXqxxPsNOnCt/AF9lqhqkFkrxa
joqvxzqGDw7jJUPZEw6ltObJn5c8Mbp7NLrfl6X4aFgjK9npeYeJKHFd/DzXgRks
BnHA4Aa6cCLP5CjJZTYVxP/ZFCUiKZosJ9kq+ahW9cLGjWg2IyaW4qvMZ/OolMzv
onVaZQKBgQCir8u1vDsyA4JQXMytPHBJe27XaLRGULvteNydVB59Vt21a99o5gt1
5B9gwWArZdZby3/KZiliNmzp8lMCrLJYjTL5WK6dbWdq92X5hCOofKPIjEcgHjhk
mvnAos3HeC83bJQtADXhw9jR7Vr6GJLM9HDcIgeIMzX7+BuqlMgaHA==
-----END RSA PRIVATE KEY-----

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{B63F7A81-1D3A-4F2F-A7C2-D6F77E5BD307}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>tls_server_example</RootNamespace>
<AssemblyName>tls_server_example</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Externalconsole>true</Externalconsole>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>full</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Externalconsole>true</Externalconsole>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
<ProjectReference Include="..\IEC61850forCSharp\IEC61850.NET.csproj">
<Project>{C35D624E-5506-4560-8074-1728F1FA1A4D}</Project>
<Name>IEC61850.NET</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="server-key.pem">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="server.cer">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="root.cer">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="model.cfg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
Loading…
Cancel
Save