diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..a83b330 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,41 @@ +name: Build and Publish + +on: + workflow_dispatch: # Allow running the workflow manually from the GitHub UI + push: + paths: + - 'Src/**' + - '.github/workflows/**' + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v4 + - name: Setup .NET 8.0 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Restore dependencies + working-directory: ./Src + run: dotnet restore + - name: Build + working-directory: ./Src + run: dotnet build --configuration Release --no-restore + - name: Test + working-directory: ./Src + run: | + dotnet test --configuration Release --no-restore --no-build --verbosity normal + #- name: Create NuGet packages + # run: | + # dotnet pack --configuration Release --output $GITHUB_WORKSPACE/out Src/SmtpServer/SmtpServer.csproj + #- name: Push NuGet packages + # run: | + # dotnet nuget push $GITHUB_WORKSPACE/out/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{secrets.NUGET_TOKEN}} --skip-duplicate --no-symbols \ No newline at end of file diff --git a/Examples/SampleApp/SampleApp.csproj b/Examples/SampleApp/SampleApp.csproj index f84b784..77cfe22 100644 --- a/Examples/SampleApp/SampleApp.csproj +++ b/Examples/SampleApp/SampleApp.csproj @@ -2,7 +2,7 @@ Exe - net5.0 + net8.0 @@ -15,7 +15,7 @@ - + diff --git a/Examples/WorkerService/WorkerService.csproj b/Examples/WorkerService/WorkerService.csproj index e5dfbb7..4aeeed1 100644 --- a/Examples/WorkerService/WorkerService.csproj +++ b/Examples/WorkerService/WorkerService.csproj @@ -1,12 +1,12 @@ - net5.0 + net8.0 dotnet-WorkerService-1397719A-187C-45F4-8DB3-2427A449DD89 - + diff --git a/Src/SmtpServer.Benchmarks/SmtpServer.Benchmarks.csproj b/Src/SmtpServer.Benchmarks/SmtpServer.Benchmarks.csproj index 3a94f4b..7dd835a 100644 --- a/Src/SmtpServer.Benchmarks/SmtpServer.Benchmarks.csproj +++ b/Src/SmtpServer.Benchmarks/SmtpServer.Benchmarks.csproj @@ -2,7 +2,7 @@ Exe - net5.0 + net8.0 @@ -20,7 +20,7 @@ - + diff --git a/Src/SmtpServer.Tests/MailClient.cs b/Src/SmtpServer.Tests/MailClient.cs index b1ee5b9..89653f5 100644 --- a/Src/SmtpServer.Tests/MailClient.cs +++ b/Src/SmtpServer.Tests/MailClient.cs @@ -1,8 +1,8 @@ -using System.Threading; -using MailKit.Net.Smtp; +using MailKit.Net.Smtp; using MailKit.Security; using MimeKit; using MimeKit.Text; +using System.Threading; namespace SmtpServer.Tests { @@ -49,7 +49,8 @@ public static MimeMessage Message( public static SmtpClientEx Client(string host = "localhost", int port = 9025, SecureSocketOptions options = SecureSocketOptions.Auto) { var client = new SmtpClientEx(); - + client.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true; + client.Connected += (sender, args) => { diff --git a/Src/SmtpServer.Tests/SmtpServer.Tests.csproj b/Src/SmtpServer.Tests/SmtpServer.Tests.csproj index 6e8b39f..38bb095 100644 --- a/Src/SmtpServer.Tests/SmtpServer.Tests.csproj +++ b/Src/SmtpServer.Tests/SmtpServer.Tests.csproj @@ -1,14 +1,14 @@  - net5.0 + net8.0 - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Src/SmtpServer.Tests/SmtpServerTests.cs b/Src/SmtpServer.Tests/SmtpServerTests.cs index ff7a60f..1c62dc7 100644 --- a/Src/SmtpServer.Tests/SmtpServerTests.cs +++ b/Src/SmtpServer.Tests/SmtpServerTests.cs @@ -1,20 +1,21 @@ -using System; +using MailKit; +using SmtpServer.Authentication; +using SmtpServer.ComponentModel; +using SmtpServer.Mail; +using SmtpServer.Net; +using SmtpServer.Protocol; +using SmtpServer.Storage; +using SmtpServer.Tests.Mocks; +using System; using System.IO; using System.Net; -using System.Net.Security; using System.Security.Authentication; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; +using System.Text; using System.Threading; using System.Threading.Tasks; -using MailKit; -using SmtpServer.Mail; -using SmtpServer.Tests.Mocks; using Xunit; -using SmtpServer.Authentication; -using SmtpServer.ComponentModel; -using SmtpServer.Net; -using SmtpServer.Protocol; -using SmtpServer.Storage; using SmtpResponse = SmtpServer.Protocol.SmtpResponse; namespace SmtpServer.Tests @@ -51,6 +52,8 @@ public void CanReceiveMessage() [InlineData("שלום שלום שלום", "windows-1255")] public void CanReceiveUnicodeMimeMessage(string text, string charset) { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + using (CreateServer()) { // act @@ -245,59 +248,49 @@ public void DoesNotSecureTheSessionWhenCertificateIsEmpty() [Fact] public void SecuresTheSessionWhenCertificateIsSupplied() { - ServicePointManager.ServerCertificateValidationCallback = IgnoreCertificateValidationFailureForTestingOnly; + using var disposable = CreateServer(options => options.Certificate(CreateCertificate())); - using (var disposable = CreateServer(options => options.Certificate(CreateCertificate()))) - { - var isSecure = false; - var sessionCreatedHandler = new EventHandler( - delegate (object sender, SessionEventArgs args) + var isSecure = false; + var sessionCreatedHandler = new EventHandler( + delegate (object sender, SessionEventArgs args) + { + args.Context.CommandExecuted += (_, commandArgs) => { - args.Context.CommandExecuted += (_, commandArgs) => - { - isSecure = commandArgs.Context.Pipe.IsSecure; - }; - }); + isSecure = commandArgs.Context.Pipe.IsSecure; + }; + }); - disposable.Server.SessionCreated += sessionCreatedHandler; - - MailClient.Send(); + disposable.Server.SessionCreated += sessionCreatedHandler; - disposable.Server.SessionCreated -= sessionCreatedHandler; - - Assert.True(isSecure); - } + MailClient.Send(); - ServicePointManager.ServerCertificateValidationCallback = null; + disposable.Server.SessionCreated -= sessionCreatedHandler; + + Assert.True(isSecure); } [Fact] public void SecuresTheSessionByDefault() { - ServicePointManager.ServerCertificateValidationCallback = IgnoreCertificateValidationFailureForTestingOnly; + using var disposable = CreateServer(endpoint => endpoint.IsSecure(true).Certificate(CreateCertificate())); - using (var disposable = CreateServer(endpoint => endpoint.IsSecure(true).Certificate(CreateCertificate()))) - { - var isSecure = false; - var sessionCreatedHandler = new EventHandler( - delegate (object sender, SessionEventArgs args) + var isSecure = false; + var sessionCreatedHandler = new EventHandler( + delegate (object sender, SessionEventArgs args) + { + args.Context.CommandExecuted += (_, commandArgs) => { - args.Context.CommandExecuted += (_, commandArgs) => - { - isSecure = commandArgs.Context.Pipe.IsSecure; - }; - }); + isSecure = commandArgs.Context.Pipe.IsSecure; + }; + }); - disposable.Server.SessionCreated += sessionCreatedHandler; - - MailClient.NoOp(MailKit.Security.SecureSocketOptions.SslOnConnect); + disposable.Server.SessionCreated += sessionCreatedHandler; - disposable.Server.SessionCreated -= sessionCreatedHandler; - - Assert.True(isSecure); - } + MailClient.NoOp(MailKit.Security.SecureSocketOptions.SslOnConnect); - ServicePointManager.ServerCertificateValidationCallback = null; + disposable.Server.SessionCreated -= sessionCreatedHandler; + + Assert.True(isSecure); } [Fact] @@ -305,35 +298,30 @@ public void ServerCanBeSecuredAndAuthenticated() { var userAuthenticator = new DelegatingUserAuthenticator((user, password) => true); - ServicePointManager.ServerCertificateValidationCallback = IgnoreCertificateValidationFailureForTestingOnly; - - using (var disposable = CreateServer( + using var disposable = CreateServer( endpoint => endpoint.AllowUnsecureAuthentication(true).Certificate(CreateCertificate()).SupportedSslProtocols(SslProtocols.Tls12), - services => services.Add(userAuthenticator))) - { - var isSecure = false; - ISessionContext sessionContext = null; - var sessionCreatedHandler = new EventHandler( - delegate (object sender, SessionEventArgs args) - { - sessionContext = args.Context; - sessionContext.CommandExecuted += (_, commandArgs) => - { - isSecure = commandArgs.Context.Pipe.IsSecure; - }; - }); + services => services.Add(userAuthenticator)); - disposable.Server.SessionCreated += sessionCreatedHandler; + var isSecure = false; + ISessionContext sessionContext = null; + var sessionCreatedHandler = new EventHandler( + delegate (object sender, SessionEventArgs args) + { + sessionContext = args.Context; + sessionContext.CommandExecuted += (_, commandArgs) => + { + isSecure = commandArgs.Context.Pipe.IsSecure; + }; + }); - MailClient.Send(user: "user", password: "password"); + disposable.Server.SessionCreated += sessionCreatedHandler; - disposable.Server.SessionCreated -= sessionCreatedHandler; + MailClient.Send(user: "user", password: "password"); - Assert.True(isSecure); - Assert.True(sessionContext.Authentication.IsAuthenticated); - } + disposable.Server.SessionCreated -= sessionCreatedHandler; - ServicePointManager.ServerCertificateValidationCallback = null; + Assert.True(isSecure); + Assert.True(sessionContext.Authentication.IsAuthenticated); } [Fact] @@ -356,17 +344,38 @@ public void EndpointListenerWillRaiseEndPointEvents() Assert.True(stopped); } - public static bool IgnoreCertificateValidationFailureForTestingOnly(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + public static X509Certificate2 CreateSelfSignedCertificate(string subjectName) { - return true; + var validityPeriodInYears = 1; + + using RSA rsa = RSA.Create(2048); // 2048-Bit Key + + var certificateRequest = new CertificateRequest( + $"CN={subjectName}", // Common Name (CN) + rsa, + HashAlgorithmName.SHA256, // Hash-Algorithmus + RSASignaturePadding.Pkcs1 // Padding Schema + ); + + certificateRequest.CertificateExtensions.Add( + new X509SubjectKeyIdentifierExtension(certificateRequest.PublicKey, false) + ); + + certificateRequest.CertificateExtensions.Add( + new X509BasicConstraintsExtension(true, false, 0, true) + ); + + DateTimeOffset notBefore = DateTimeOffset.UtcNow; + DateTimeOffset notAfter = notBefore.AddYears(validityPeriodInYears); + + X509Certificate2 certificate = certificateRequest.CreateSelfSigned(notBefore, notAfter); + + return new X509Certificate2(certificate.Export(X509ContentType.Pfx)); } public static X509Certificate2 CreateCertificate() { - var certificate = File.ReadAllBytes(@"C:\Users\caino\Dropbox\Documents\Cain\Programming\SmtpServer\SmtpServer.pfx"); - var password = File.ReadAllText(@"C:\Users\caino\Dropbox\Documents\Cain\Programming\SmtpServer\SmtpServerPassword.txt"); - - return new X509Certificate2(certificate, password); + return CreateSelfSignedCertificate("localhost"); } ///