diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj
index 5e15896..fcac937 100644
--- a/src/Core/Core.csproj
+++ b/src/Core/Core.csproj
@@ -8,7 +8,9 @@
-
+
+
+
diff --git a/src/Core/Extensions/ObjectExtensions.cs b/src/Core/Extensions/ObjectExtensions.cs
index d604210..0c5f2b1 100644
--- a/src/Core/Extensions/ObjectExtensions.cs
+++ b/src/Core/Extensions/ObjectExtensions.cs
@@ -1,37 +1,36 @@
using System.Text.Json.Serialization;
using System.Text.Json;
-namespace MaksIT.Core.Extensions {
- public static class ObjectExtensions {
+namespace MaksIT.Core.Extensions;
+public static class ObjectExtensions {
- ///
- ///
- ///
- ///
- ///
- ///
- public static string ToJson(this T? obj) => obj.ToJson(null);
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static string ToJson(this T? obj) => obj.ToJson(null);
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public static string ToJson(this T? obj, List? converters) {
- if (obj == null)
- return "{}";
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static string ToJson(this T? obj, List? converters) {
+ if (obj == null)
+ return "{}";
- var options = new JsonSerializerOptions {
- PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
- DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
- WriteIndented = true
- };
+ var options = new JsonSerializerOptions {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ WriteIndented = true
+ };
- converters?.ForEach(x => options.Converters.Add(x));
+ converters?.ForEach(x => options.Converters.Add(x));
- return JsonSerializer.Serialize(obj, options);
- }
+ return JsonSerializer.Serialize(obj, options);
}
}
diff --git a/src/Core/Extensions/StringExtensions.cs b/src/Core/Extensions/StringExtensions.cs
index a8d5126..fb178e3 100644
--- a/src/Core/Extensions/StringExtensions.cs
+++ b/src/Core/Extensions/StringExtensions.cs
@@ -6,35 +6,34 @@ using System.Text.Json.Serialization;
using System.Text.Json;
using System.Threading.Tasks;
-namespace MaksIT.Core.Extensions {
- public static class StringExtensions {
- ///
- /// Converts JSON string to object
- ///
- ///
- ///
- ///
- public static T? ToObject(this string? s) => ToObjectCore(s, null);
+namespace MaksIT.Core.Extensions;
+public static class StringExtensions {
+ ///
+ /// Converts JSON string to object
+ ///
+ ///
+ ///
+ ///
+ public static T? ToObject(this string? s) => ToObjectCore(s, null);
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public static T? ToObject(this string? s, List converters) => ToObjectCore(s, converters);
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static T? ToObject(this string? s, List converters) => ToObjectCore(s, converters);
- private static T? ToObjectCore(string? s, List? converters) {
- var options = new JsonSerializerOptions {
- PropertyNameCaseInsensitive = true
- };
+ private static T? ToObjectCore(string? s, List? converters) {
+ var options = new JsonSerializerOptions {
+ PropertyNameCaseInsensitive = true
+ };
- converters?.ForEach(x => options.Converters.Add(x));
+ converters?.ForEach(x => options.Converters.Add(x));
- return s != null
- ? JsonSerializer.Deserialize(s, options)
- : default;
- }
+ return s != null
+ ? JsonSerializer.Deserialize(s, options)
+ : default;
}
}
diff --git a/src/Core/OperatingSystem.cs b/src/Core/OperatingSystem.cs
index 162e2c1..fb45bec 100644
--- a/src/Core/OperatingSystem.cs
+++ b/src/Core/OperatingSystem.cs
@@ -1,14 +1,13 @@
using System.Runtime.InteropServices;
-namespace MaksIT.Core {
- public static class OperatingSystem {
- public static bool IsWindows() =>
- RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+namespace MaksIT.Core;
+public static class OperatingSystem {
+ public static bool IsWindows() =>
+ RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
- public static bool IsMacOS() =>
- RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
+ public static bool IsMacOS() =>
+ RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
- public static bool IsLinux() =>
- RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
- }
+ public static bool IsLinux() =>
+ RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
}
diff --git a/src/LetsEncrypt/Entities/Jws/Jwk.cs b/src/LetsEncrypt/Entities/Jws/Jwk.cs
index 2b4d807..0907727 100644
--- a/src/LetsEncrypt/Entities/Jws/Jwk.cs
+++ b/src/LetsEncrypt/Entities/Jws/Jwk.cs
@@ -2,104 +2,103 @@
using System.Text.Json.Serialization;
-namespace MaksIT.LetsEncrypt.Entities.Jws {
- public class Jwk {
- ///
- /// "kty" (Key Type) Parameter
- ///
- /// The "kty" (key type) parameter identifies the cryptographic algorithm
- /// family used with the key, such as "RSA" or "EC".
- ///
- ///
- [JsonPropertyName("kty")]
- public string? KeyType { get; set; }
+namespace MaksIT.LetsEncrypt.Entities.Jws;
+public class Jwk {
+ ///
+ /// "kty" (Key Type) Parameter
+ ///
+ /// The "kty" (key type) parameter identifies the cryptographic algorithm
+ /// family used with the key, such as "RSA" or "EC".
+ ///
+ ///
+ [JsonPropertyName("kty")]
+ public string? KeyType { get; set; }
- ///
- /// "kid" (Key ID) Parameter
- ///
- /// The "kid" (key ID) parameter is used to match a specific key. This
- /// is used, for instance, to choose among a set of keys within a JWK Set
- /// during key rollover. The structure of the "kid" value is
- /// unspecified.
- ///
- ///
- [JsonPropertyName("kid")]
- public string? KeyId { get; set; }
+ ///
+ /// "kid" (Key ID) Parameter
+ ///
+ /// The "kid" (key ID) parameter is used to match a specific key. This
+ /// is used, for instance, to choose among a set of keys within a JWK Set
+ /// during key rollover. The structure of the "kid" value is
+ /// unspecified.
+ ///
+ ///
+ [JsonPropertyName("kid")]
+ public string? KeyId { get; set; }
- ///
- /// "use" (Public Key Use) Parameter
- ///
- /// The "use" (public key use) parameter identifies the intended use of
- /// the public key. The "use" parameter is employed to indicate whether
- /// a public key is used for encrypting data or verifying the signature
- /// on data.
- ///
- ///
- [JsonPropertyName("use")]
- public string? Use { get; set; }
+ ///
+ /// "use" (Public Key Use) Parameter
+ ///
+ /// The "use" (public key use) parameter identifies the intended use of
+ /// the public key. The "use" parameter is employed to indicate whether
+ /// a public key is used for encrypting data or verifying the signature
+ /// on data.
+ ///
+ ///
+ [JsonPropertyName("use")]
+ public string? Use { get; set; }
- ///
- /// The the modulus value for the public RSA key. It is represented as the Base64URL encoding of value's big endian representation.
- ///
- [JsonPropertyName("n")]
- public string? Modulus { get; set; }
+ ///
+ /// The the modulus value for the public RSA key. It is represented as the Base64URL encoding of value's big endian representation.
+ ///
+ [JsonPropertyName("n")]
+ public string? Modulus { get; set; }
- ///
- /// The exponent value for the public RSA key. It is represented as the Base64URL encoding of value's big endian representation.
- ///
- [JsonPropertyName("e")]
- public string? Exponent { get; set; }
+ ///
+ /// The exponent value for the public RSA key. It is represented as the Base64URL encoding of value's big endian representation.
+ ///
+ [JsonPropertyName("e")]
+ public string? Exponent { get; set; }
- ///
- /// The private exponent. It is represented as the Base64URL encoding of the value's big endian representation.
- ///
- [JsonPropertyName("d")]
- public string? D { get; set; }
+ ///
+ /// The private exponent. It is represented as the Base64URL encoding of the value's big endian representation.
+ ///
+ [JsonPropertyName("d")]
+ public string? D { get; set; }
- ///
- /// The first prime factor. It is represented as the Base64URL encoding of the value's big endian representation.
- ///
- [JsonPropertyName("p")]
- public string? P { get; set; }
+ ///
+ /// The first prime factor. It is represented as the Base64URL encoding of the value's big endian representation.
+ ///
+ [JsonPropertyName("p")]
+ public string? P { get; set; }
- ///
- /// The second prime factor. It is represented as the Base64URL encoding of the value's big endian representation.
- ///
- [JsonPropertyName("q")]
- public string? Q { get; set; }
+ ///
+ /// The second prime factor. It is represented as the Base64URL encoding of the value's big endian representation.
+ ///
+ [JsonPropertyName("q")]
+ public string? Q { get; set; }
- ///
- /// The first factor Chinese Remainder Theorem exponent. It is represented as the Base64URL encoding of the value's big endian representation.
- ///
- [JsonPropertyName("dp")]
- public string? DP { get; set; }
+ ///
+ /// The first factor Chinese Remainder Theorem exponent. It is represented as the Base64URL encoding of the value's big endian representation.
+ ///
+ [JsonPropertyName("dp")]
+ public string? DP { get; set; }
- ///
- /// The second factor Chinese Remainder Theorem exponent. It is represented as the Base64URL encoding of the value's big endian representation.
- ///
- [JsonPropertyName("dq")]
- public string? DQ { get; set; }
+ ///
+ /// The second factor Chinese Remainder Theorem exponent. It is represented as the Base64URL encoding of the value's big endian representation.
+ ///
+ [JsonPropertyName("dq")]
+ public string? DQ { get; set; }
- ///
- /// The first Chinese Remainder Theorem coefficient. It is represented as the Base64URL encoding of the value's big endian representation.
- ///
- [JsonPropertyName("qi")]
- public string? InverseQ { get; set; }
+ ///
+ /// The first Chinese Remainder Theorem coefficient. It is represented as the Base64URL encoding of the value's big endian representation.
+ ///
+ [JsonPropertyName("qi")]
+ public string? InverseQ { get; set; }
- ///
- /// The other primes information, should they exist, null or an empty list if not specified.
- ///
- [JsonPropertyName("oth")]
- public string? OthInf { get; set; }
+ ///
+ /// The other primes information, should they exist, null or an empty list if not specified.
+ ///
+ [JsonPropertyName("oth")]
+ public string? OthInf { get; set; }
- ///
- /// "alg" (Algorithm) Parameter
- ///
- /// The "alg" (algorithm) parameter identifies the algorithm intended for
- /// use with the key.
- ///
- ///
- [JsonPropertyName("alg")]
- public string? Algorithm { get; set; }
- }
-}
\ No newline at end of file
+ ///
+ /// "alg" (Algorithm) Parameter
+ ///
+ /// The "alg" (algorithm) parameter identifies the algorithm intended for
+ /// use with the key.
+ ///
+ ///
+ [JsonPropertyName("alg")]
+ public string? Algorithm { get; set; }
+}
diff --git a/src/LetsEncrypt/Entities/Jws/JwsMessage.cs b/src/LetsEncrypt/Entities/Jws/JwsMessage.cs
index afd3d25..4726d5e 100644
--- a/src/LetsEncrypt/Entities/Jws/JwsMessage.cs
+++ b/src/LetsEncrypt/Entities/Jws/JwsMessage.cs
@@ -1,40 +1,36 @@
using System;
using System.Text.Json.Serialization;
-namespace MaksIT.LetsEncrypt.Entities.Jws
-{
-
- public class JwsMessage {
-
- public string? Protected { get; set; }
-
- public string? Payload { get; set; }
-
- public string? Signature { get; set; }
- }
+namespace MaksIT.LetsEncrypt.Entities.Jws;
- public class JwsHeader {
-
- [JsonPropertyName("alg")]
- public string? Algorithm { get; set; }
-
- [JsonPropertyName("jwk")]
- public Jwk? Key { get; set; }
-
-
- [JsonPropertyName("kid")]
- public string? KeyId { get; set; }
-
- public string? Nonce { get; set; }
-
- public Uri? Url { get; set; }
-
-
- [JsonPropertyName("Host")]
- public string? Host { get; set; }
- }
+public class JwsMessage {
+ public string? Protected { get; set; }
+ public string? Payload { get; set; }
+ public string? Signature { get; set; }
+}
+
+
+public class JwsHeader {
+
+ [JsonPropertyName("alg")]
+ public string? Algorithm { get; set; }
+
+ [JsonPropertyName("jwk")]
+ public Jwk? Key { get; set; }
+
+
+ [JsonPropertyName("kid")]
+ public string? KeyId { get; set; }
+
+ public string? Nonce { get; set; }
+
+ public Uri? Url { get; set; }
+
+
+ [JsonPropertyName("Host")]
+ public string? Host { get; set; }
}
diff --git a/src/LetsEncrypt/Entities/LetsEncrypt/AuthorizationChallange.cs b/src/LetsEncrypt/Entities/LetsEncrypt/AuthorizationChallange.cs
index decc2ed..c937703 100644
--- a/src/LetsEncrypt/Entities/LetsEncrypt/AuthorizationChallange.cs
+++ b/src/LetsEncrypt/Entities/LetsEncrypt/AuthorizationChallange.cs
@@ -1,13 +1,12 @@
using System;
-namespace MaksIT.LetsEncrypt.Entities {
- public class AuthorizationChallenge {
- public Uri? Url { get; set; }
+namespace MaksIT.LetsEncrypt.Entities;
+public class AuthorizationChallenge {
+ public Uri? Url { get; set; }
- public string? Type { get; set; }
+ public string? Type { get; set; }
- public string? Status { get; set; }
+ public string? Status { get; set; }
- public string? Token { get; set; }
- }
+ public string? Token { get; set; }
}
diff --git a/src/LetsEncrypt/Entities/LetsEncrypt/CachedCertificateResult.cs b/src/LetsEncrypt/Entities/LetsEncrypt/CachedCertificateResult.cs
index abb7436..5b8fbe2 100644
--- a/src/LetsEncrypt/Entities/LetsEncrypt/CachedCertificateResult.cs
+++ b/src/LetsEncrypt/Entities/LetsEncrypt/CachedCertificateResult.cs
@@ -1,11 +1,8 @@
using System.Security.Cryptography;
-namespace MaksIT.LetsEncrypt.Entities
-{
- public class CachedCertificateResult
- {
- public RSACryptoServiceProvider? PrivateKey { get; set; }
- public string? Certificate { get; set; }
- }
+namespace MaksIT.LetsEncrypt.Entities;
+public class CachedCertificateResult {
+ public RSACryptoServiceProvider? PrivateKey { get; set; }
+ public string? Certificate { get; set; }
}
diff --git a/src/LetsEncrypt/Entities/LetsEncrypt/RegistrationCache.cs b/src/LetsEncrypt/Entities/LetsEncrypt/RegistrationCache.cs
index 62b348a..6aee7b4 100644
--- a/src/LetsEncrypt/Entities/LetsEncrypt/RegistrationCache.cs
+++ b/src/LetsEncrypt/Entities/LetsEncrypt/RegistrationCache.cs
@@ -1,18 +1,62 @@
using System;
using System.Collections.Generic;
+using System.Security.Cryptography.X509Certificates;
+using System.Security.Cryptography;
+using System.Text;
using MaksIT.LetsEncrypt.Entities.Jws;
-namespace MaksIT.LetsEncrypt.Entities {
- public class CertificateCache {
- public string? Cert { get; set; }
- public byte[]? Private { get; set; }
+namespace MaksIT.LetsEncrypt.Entities;
+public class CertificateCache {
+ public string? Cert { get; set; }
+ public byte[]? Private { get; set; }
+}
+
+public class RegistrationCache {
+ public Dictionary? CachedCerts { get; set; }
+ public byte[]? AccountKey { get; set; }
+ public string? Id { get; set; }
+ public Jwk? Key { get; set; }
+ public Uri? Location { get; set; }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool TryGetCachedCertificate(string subject, out CachedCertificateResult? value) {
+ value = null;
+
+ if (CachedCerts == null)
+ return false;
+
+ if (!CachedCerts.TryGetValue(subject, out var cache)) {
+ return false;
+ }
+
+ var cert = new X509Certificate2(Encoding.ASCII.GetBytes(cache.Cert));
+
+ // if it is about to expire, we need to refresh
+ if ((cert.NotAfter - DateTime.UtcNow).TotalDays < 30)
+ return false;
+
+ var rsa = new RSACryptoServiceProvider(4096);
+ rsa.ImportCspBlob(cache.Private);
+
+ value = new CachedCertificateResult {
+ Certificate = cache.Cert,
+ PrivateKey = rsa
+ };
+ return true;
}
- public class RegistrationCache {
- public Dictionary? CachedCerts { get; set; }
- public byte[]? AccountKey { get; set; }
- public string? Id { get; set; }
- public Jwk? Key { get; set; }
- public Uri? Location { get; set; }
+ ///
+ ///
+ ///
+ ///
+ public void ResetCachedCertificate(IEnumerable hostsToRemove) {
+ if (CachedCerts != null)
+ foreach (var host in hostsToRemove)
+ CachedCerts.Remove(host);
}
}
diff --git a/src/LetsEncrypt/Entities/LetsEncrypt/SendResult.cs b/src/LetsEncrypt/Entities/LetsEncrypt/SendResult.cs
new file mode 100644
index 0000000..dd55fd1
--- /dev/null
+++ b/src/LetsEncrypt/Entities/LetsEncrypt/SendResult.cs
@@ -0,0 +1,10 @@
+namespace MaksIT.LetsEncrypt.Entities {
+ public class SendResult {
+
+ public TResult? Result { get; set; }
+
+ public string? ResponseText { get; set; }
+
+
+ }
+}
diff --git a/src/LetsEncrypt/Exceptions/LetsEncrytException.cs b/src/LetsEncrypt/Exceptions/LetsEncrytException.cs
index 615f312..184dbb9 100644
--- a/src/LetsEncrypt/Exceptions/LetsEncrytException.cs
+++ b/src/LetsEncrypt/Exceptions/LetsEncrytException.cs
@@ -1,25 +1,19 @@
-using System;
-using System.Net.Http;
+using MaksIT.Core.Extensions;
+using MaksIT.LetsEncrypt.Models.Responses;
-namespace MaksIT.LetsEncrypt.Exceptions {
- public class LetsEncrytException : Exception {
- public LetsEncrytException(Problem problem, HttpResponseMessage response)
- : base($"{problem.Type}: {problem.Detail}") {
- Problem = problem;
- Response = response;
- }
+namespace MaksIT.LetsEncrypt.Exceptions;
+public class LetsEncrytException : Exception {
- public Problem Problem { get; }
+ public Problem? Problem { get; }
- public HttpResponseMessage Response { get; }
- }
+ public HttpResponseMessage Response { get; }
+ public LetsEncrytException(
+ Problem? problem,
+ HttpResponseMessage response
+ ) : base(problem != null ? $"{problem.Type}: {problem.Detail}" : "") {
- public class Problem {
- public string Type { get; set; }
-
- public string Detail { get; set; }
-
- public string RawJson { get; set; }
+ Problem = problem;
+ Response = response;
}
}
diff --git a/src/LetsEncrypt/Extensions/ServiceCollectionExtensions.cs b/src/LetsEncrypt/Extensions/ServiceCollectionExtensions.cs
index 9bcc721..99f3de4 100644
--- a/src/LetsEncrypt/Extensions/ServiceCollectionExtensions.cs
+++ b/src/LetsEncrypt/Extensions/ServiceCollectionExtensions.cs
@@ -2,11 +2,10 @@
using MaksIT.LetsEncrypt.Services;
-namespace MaksIT.LetsEncrypt.Extensions {
- public static class ServiceCollectionExtensions {
- public static void RegisterLetsEncrypt(this IServiceCollection services) {
+namespace MaksIT.LetsEncrypt.Extensions;
+public static class ServiceCollectionExtensions {
+ public static void RegisterLetsEncrypt(this IServiceCollection services) {
- services.AddHttpClient();
- }
+ services.AddHttpClient();
}
}
diff --git a/src/LetsEncrypt/LetsEncrypt.csproj b/src/LetsEncrypt/LetsEncrypt.csproj
index ae5e10f..54bddde 100644
--- a/src/LetsEncrypt/LetsEncrypt.csproj
+++ b/src/LetsEncrypt/LetsEncrypt.csproj
@@ -8,8 +8,9 @@
+
-
+
diff --git a/src/LetsEncrypt/Models/Interfaces/RequestModelWithLocationBase.cs b/src/LetsEncrypt/Models/Interfaces/RequestModelWithLocationBase.cs
index 0fcaa1f..b06d292 100644
--- a/src/LetsEncrypt/Models/Interfaces/RequestModelWithLocationBase.cs
+++ b/src/LetsEncrypt/Models/Interfaces/RequestModelWithLocationBase.cs
@@ -1,5 +1,4 @@
-namespace MaksIT.LetsEncrypt.Models.Interfaces {
- public interface IHasLocation {
- Uri? Location { get; set; }
- }
+namespace MaksIT.LetsEncrypt.Models.Interfaces;
+public interface IHasLocation {
+ Uri? Location { get; set; }
}
diff --git a/src/LetsEncrypt/Models/Requests/FinalizeRequest.cs b/src/LetsEncrypt/Models/Requests/FinalizeRequest.cs
index 6ffeb01..de09b97 100644
--- a/src/LetsEncrypt/Models/Requests/FinalizeRequest.cs
+++ b/src/LetsEncrypt/Models/Requests/FinalizeRequest.cs
@@ -1,7 +1,5 @@
-namespace MaksIT.LetsEncrypt.Models.Requests
-{
- public class FinalizeRequest
- {
- public string? Csr { get; set; }
- }
+namespace MaksIT.LetsEncrypt.Models.Requests;
+
+public class FinalizeRequest {
+ public string? Csr { get; set; }
}
diff --git a/src/LetsEncrypt/Models/Responses/Account.cs b/src/LetsEncrypt/Models/Responses/Account.cs
index 8594c90..b00fae4 100644
--- a/src/LetsEncrypt/Models/Responses/Account.cs
+++ b/src/LetsEncrypt/Models/Responses/Account.cs
@@ -5,34 +5,33 @@ using MaksIT.LetsEncrypt.Models.Interfaces;
* https://tools.ietf.org/html/draft-ietf-acme-acme-18#section-7.3
*/
-namespace MaksIT.LetsEncrypt.Models.Responses
-{
- public class Account : IHasLocation {
+namespace MaksIT.LetsEncrypt.Models.Responses;
- public bool TermsOfServiceAgreed { get; set; }
+public class Account : IHasLocation {
- /*
- onlyReturnExisting (optional, boolean): If this field is present
- with the value "true", then the server MUST NOT create a new
- account if one does not already exist. This allows a client to
- look up an account URL based on an account key
- */
- public bool OnlyReturnExisting { get; set; }
+ public bool TermsOfServiceAgreed { get; set; }
- public string[]? Contacts { get; set; }
+ /*
+ onlyReturnExisting (optional, boolean): If this field is present
+ with the value "true", then the server MUST NOT create a new
+ account if one does not already exist. This allows a client to
+ look up an account URL based on an account key
+ */
+ public bool OnlyReturnExisting { get; set; }
- public string? Status { get; set; }
+ public string[]? Contacts { get; set; }
- public string? Id { get; set; }
+ public string? Status { get; set; }
- public DateTime CreatedAt { get; set; }
+ public string? Id { get; set; }
- public Jwk? Key { get; set; }
+ public DateTime CreatedAt { get; set; }
- public string? InitialIp { get; set; }
+ public Jwk? Key { get; set; }
- public Uri? Orders { get; set; }
+ public string? InitialIp { get; set; }
- public Uri? Location { get; set; }
- }
+ public Uri? Orders { get; set; }
+
+ public Uri? Location { get; set; }
}
diff --git a/src/LetsEncrypt/Models/Responses/AcmeDirectory.cs b/src/LetsEncrypt/Models/Responses/AcmeDirectory.cs
index 065e063..fc35900 100644
--- a/src/LetsEncrypt/Models/Responses/AcmeDirectory.cs
+++ b/src/LetsEncrypt/Models/Responses/AcmeDirectory.cs
@@ -1,29 +1,25 @@
using System;
-namespace MaksIT.LetsEncrypt.Models.Responses
-{
- public class AcmeDirectory
- {
- public Uri NewNonce { get; set; }
+namespace MaksIT.LetsEncrypt.Models.Responses;
+public class AcmeDirectory {
+ public Uri NewNonce { get; set; }
- public Uri NewAccount { get; set; }
+ public Uri NewAccount { get; set; }
- public Uri NewOrder { get; set; }
+ public Uri NewOrder { get; set; }
- // New authorization If the ACME server does not implement pre-authorization
- // (Section 7.4.1) it MUST omit the "newAuthz" field of the directory.
- // [JsonProperty("newAuthz")]
- // public Uri NewAuthz { get; set; }
- public Uri RevokeCertificate { get; set; }
+ // New authorization If the ACME server does not implement pre-authorization
+ // (Section 7.4.1) it MUST omit the "newAuthz" field of the directory.
+ // [JsonProperty("newAuthz")]
+ // public Uri NewAuthz { get; set; }
+ public Uri RevokeCertificate { get; set; }
- public Uri KeyChange { get; set; }
+ public Uri KeyChange { get; set; }
- public AcmeDirectoryMeta Meta { get; set; }
- }
-
- public class AcmeDirectoryMeta
- {
- public string TermsOfService { get; set; }
- }
+ public AcmeDirectoryMeta Meta { get; set; }
}
+
+public class AcmeDirectoryMeta {
+ public string TermsOfService { get; set; }
+}
\ No newline at end of file
diff --git a/src/LetsEncrypt/Models/Responses/AuthorizationChallengeResponse.cs b/src/LetsEncrypt/Models/Responses/AuthorizationChallengeResponse.cs
index 211ae81..c6dba8b 100644
--- a/src/LetsEncrypt/Models/Responses/AuthorizationChallengeResponse.cs
+++ b/src/LetsEncrypt/Models/Responses/AuthorizationChallengeResponse.cs
@@ -1,20 +1,20 @@
using MaksIT.LetsEncrypt.Entities;
-namespace MaksIT.LetsEncrypt.Models.Responses {
- public class AuthorizationChallengeResponse {
- public OrderIdentifier? Identifier { get; set; }
+namespace MaksIT.LetsEncrypt.Models.Responses;
- public string? Status { get; set; }
+public class AuthorizationChallengeResponse {
+ public OrderIdentifier? Identifier { get; set; }
- public DateTime? Expires { get; set; }
+ public string? Status { get; set; }
- public bool Wildcard { get; set; }
+ public DateTime? Expires { get; set; }
- public AuthorizationChallenge[]? Challenges { get; set; }
- }
+ public bool Wildcard { get; set; }
- public class AuthorizeChallenge {
- public string? KeyAuthorization { get; set; }
- }
+ public AuthorizationChallenge[]? Challenges { get; set; }
+}
+
+public class AuthorizeChallenge {
+ public string? KeyAuthorization { get; set; }
}
diff --git a/src/LetsEncrypt/Models/Responses/Order.cs b/src/LetsEncrypt/Models/Responses/Order.cs
index 12d316a..ea2236a 100644
--- a/src/LetsEncrypt/Models/Responses/Order.cs
+++ b/src/LetsEncrypt/Models/Responses/Order.cs
@@ -1,34 +1,33 @@
using MaksIT.LetsEncrypt.Exceptions;
using MaksIT.LetsEncrypt.Models.Interfaces;
-namespace MaksIT.LetsEncrypt.Models.Responses {
+namespace MaksIT.LetsEncrypt.Models.Responses;
- public class OrderIdentifier {
- public string? Type { get; set; }
+public class OrderIdentifier {
+ public string? Type { get; set; }
- public string? Value { get; set; }
+ public string? Value { get; set; }
- }
-
- public class Order : IHasLocation {
- public Uri? Location { get; set; }
-
- public string? Status { get; set; }
-
- public DateTime? Expires { get; set; }
-
- public OrderIdentifier[]? Identifiers { get; set; }
-
- public DateTime? NotBefore { get; set; }
-
- public DateTime? NotAfter { get; set; }
-
- public Problem? Error { get; set; }
-
- public Uri[]? Authorizations { get; set; }
-
- public Uri? Finalize { get; set; }
-
- public Uri? Certificate { get; set; }
- }
+}
+
+public class Order : IHasLocation {
+ public Uri? Location { get; set; }
+
+ public string? Status { get; set; }
+
+ public DateTime? Expires { get; set; }
+
+ public OrderIdentifier[]? Identifiers { get; set; }
+
+ public DateTime? NotBefore { get; set; }
+
+ public DateTime? NotAfter { get; set; }
+
+ public Problem? Error { get; set; }
+
+ public Uri[]? Authorizations { get; set; }
+
+ public Uri? Finalize { get; set; }
+
+ public Uri? Certificate { get; set; }
}
diff --git a/src/LetsEncrypt/Models/Responses/Problem.cs b/src/LetsEncrypt/Models/Responses/Problem.cs
new file mode 100644
index 0000000..fa7811b
--- /dev/null
+++ b/src/LetsEncrypt/Models/Responses/Problem.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MaksIT.LetsEncrypt.Models.Responses {
+ public class Problem {
+ public string Type { get; set; }
+
+ public string Detail { get; set; }
+
+ public string RawJson { get; set; }
+ }
+}
diff --git a/src/LetsEncrypt/Services/JwsService.cs b/src/LetsEncrypt/Services/JwsService.cs
index b1d775c..73720f5 100644
--- a/src/LetsEncrypt/Services/JwsService.cs
+++ b/src/LetsEncrypt/Services/JwsService.cs
@@ -11,102 +11,99 @@ using MaksIT.LetsEncrypt.Entities.Jws;
using MaksIT.Core.Extensions;
-namespace MaksIT.LetsEncrypt.Services {
- public interface IJwsService {
- void SetKeyId(string location);
+namespace MaksIT.LetsEncrypt.Services;
- JwsMessage Encode(JwsHeader protectedHeader);
+public interface IJwsService {
+ void SetKeyId(string location);
- JwsMessage Encode(TPayload payload, JwsHeader protectedHeader);
+ JwsMessage Encode(JwsHeader protectedHeader);
- string GetKeyAuthorization(string token);
+ JwsMessage Encode(TPayload payload, JwsHeader protectedHeader);
+
+ string GetKeyAuthorization(string token);
- string Base64UrlEncoded(string s);
+ string Base64UrlEncoded(string s);
- string Base64UrlEncoded(byte[] arg);
- }
-
-
- public class JwsService : IJwsService {
-
- public Jwk _jwk;
- private RSA _rsa;
-
- public JwsService(RSA rsa) {
- _rsa = rsa ?? throw new ArgumentNullException(nameof(rsa));
-
- var publicParameters = rsa.ExportParameters(false);
-
- var exp = publicParameters.Exponent ?? throw new ArgumentNullException(nameof(publicParameters.Exponent));
- var mod = publicParameters.Modulus ?? throw new ArgumentNullException(nameof(publicParameters.Modulus));
-
- _jwk = new Jwk() {
- KeyType = "RSA",
- Exponent = Base64UrlEncoded(exp),
- Modulus = Base64UrlEncoded(mod),
- };
- }
-
- public void SetKeyId(string location) {
- _jwk.KeyId = location;
- }
-
- public JwsMessage Encode(JwsHeader protectedHeader) =>
- Encode(null, protectedHeader);
-
- public JwsMessage Encode(T? payload, JwsHeader protectedHeader) {
-
- protectedHeader.Algorithm = "RS256";
- if (_jwk.KeyId != null) {
- protectedHeader.KeyId = _jwk.KeyId;
- }
- else {
- protectedHeader.Key = _jwk;
- }
-
- var message = new JwsMessage {
- Payload = "",
- Protected = Base64UrlEncoded(protectedHeader.ToJson())
- };
-
- if (payload != null) {
- if (payload is string stringPayload)
- message.Payload = Base64UrlEncoded(stringPayload);
- else
- message.Payload = Base64UrlEncoded(payload.ToJson());
- }
-
-
- message.Signature = Base64UrlEncoded(
- _rsa.SignData(Encoding.ASCII.GetBytes($"{message.Protected}.{message.Payload}"),
- HashAlgorithmName.SHA256,
- RSASignaturePadding.Pkcs1));
-
- return message;
- }
-
- public string GetKeyAuthorization(string token) =>
- $"{token}.{GetSha256Thumbprint()}";
-
- private string GetSha256Thumbprint() {
- var json = "{\"e\":\"" + _jwk.Exponent + "\",\"kty\":\"RSA\",\"n\":\"" + _jwk.Modulus + "\"}";
- return Base64UrlEncoded(SHA256.HashData(Encoding.UTF8.GetBytes(json)));
- }
-
-
-
- public string Base64UrlEncoded(string s) =>
- Base64UrlEncoded(Encoding.UTF8.GetBytes(s));
-
- // https://tools.ietf.org/html/rfc4648#section-5
- public string Base64UrlEncoded(byte[] bytes) =>
- Convert.ToBase64String(bytes) // Regular base64 encoder
- .Split('=').First() // Remove any trailing '='s
- .Replace('+', '-') // 62nd char of encoding
- .Replace('/', '_'); // 63rd char of encoding
-
-
-
- }
+ string Base64UrlEncoded(byte[] arg);
+}
+
+
+public class JwsService : IJwsService {
+
+ public Jwk _jwk;
+ private RSA _rsa;
+
+ public JwsService(RSA rsa) {
+ _rsa = rsa ?? throw new ArgumentNullException(nameof(rsa));
+
+ var publicParameters = rsa.ExportParameters(false);
+
+ var exp = publicParameters.Exponent ?? throw new ArgumentNullException(nameof(publicParameters.Exponent));
+ var mod = publicParameters.Modulus ?? throw new ArgumentNullException(nameof(publicParameters.Modulus));
+
+ _jwk = new Jwk() {
+ KeyType = "RSA",
+ Exponent = Base64UrlEncoded(exp),
+ Modulus = Base64UrlEncoded(mod),
+ };
+ }
+
+ public void SetKeyId(string location) {
+ _jwk.KeyId = location;
+ }
+
+ public JwsMessage Encode(JwsHeader protectedHeader) =>
+ Encode(null, protectedHeader);
+
+ public JwsMessage Encode(T? payload, JwsHeader protectedHeader) {
+
+ protectedHeader.Algorithm = "RS256";
+ if (_jwk.KeyId != null) {
+ protectedHeader.KeyId = _jwk.KeyId;
+ }
+ else {
+ protectedHeader.Key = _jwk;
+ }
+
+ var message = new JwsMessage {
+ Payload = "",
+ Protected = Base64UrlEncoded(protectedHeader.ToJson())
+ };
+
+ if (payload != null) {
+ if (payload is string stringPayload)
+ message.Payload = Base64UrlEncoded(stringPayload);
+ else
+ message.Payload = Base64UrlEncoded(payload.ToJson());
+ }
+
+
+ message.Signature = Base64UrlEncoded(
+ _rsa.SignData(Encoding.ASCII.GetBytes($"{message.Protected}.{message.Payload}"),
+ HashAlgorithmName.SHA256,
+ RSASignaturePadding.Pkcs1));
+
+ return message;
+ }
+
+ public string GetKeyAuthorization(string token) =>
+ $"{token}.{GetSha256Thumbprint()}";
+
+ private string GetSha256Thumbprint() {
+ var json = "{\"e\":\"" + _jwk.Exponent + "\",\"kty\":\"RSA\",\"n\":\"" + _jwk.Modulus + "\"}";
+ return Base64UrlEncoded(SHA256.HashData(Encoding.UTF8.GetBytes(json)));
+ }
+
+
+
+ public string Base64UrlEncoded(string s) =>
+ Base64UrlEncoded(Encoding.UTF8.GetBytes(s));
+
+ // https://tools.ietf.org/html/rfc4648#section-5
+ public string Base64UrlEncoded(byte[] bytes) =>
+ Convert.ToBase64String(bytes) // Regular base64 encoder
+ .Split('=').First() // Remove any trailing '='s
+ .Replace('+', '-') // 62nd char of encoding
+ .Replace('/', '_'); // 63rd char of encoding
}
diff --git a/src/LetsEncrypt/Services/LetsEncryptService.cs b/src/LetsEncrypt/Services/LetsEncryptService.cs
index 24c58e1..ac27dc2 100644
--- a/src/LetsEncrypt/Services/LetsEncryptService.cs
+++ b/src/LetsEncrypt/Services/LetsEncryptService.cs
@@ -18,86 +18,100 @@ using MaksIT.LetsEncrypt.Models.Responses;
using MaksIT.LetsEncrypt.Models.Interfaces;
using MaksIT.LetsEncrypt.Models.Requests;
using MaksIT.LetsEncrypt.Entities.Jws;
-using System.Xml;
-using System.Diagnostics;
+using DomainResults.Common;
-namespace MaksIT.LetsEncrypt.Services {
+namespace MaksIT.LetsEncrypt.Services;
- public interface ILetsEncryptService {
+public interface ILetsEncryptService {
- Task ConfigureClient(string url);
+ Task ConfigureClient(string url);
- Task Init(string[] contacts, RegistrationCache? registrationCache);
+ Task Init(string[] contacts, RegistrationCache? registrationCache);
- string GetTermsOfServiceUri();
+ RegistrationCache? GetRegistrationCache();
+
+ (string?, IDomainResult) GetTermsOfServiceUri();
- Task> NewOrder(string[] hostnames, string challengeType);
- Task CompleteChallenges();
- Task GetOrder(string[] hostnames);
- Task<(X509Certificate2 Cert, RSA PrivateKey)> GetCertificate(string subject);
+ Task<(Dictionary?, IDomainResult)> NewOrder(string[] hostnames, string challengeType);
+ Task CompleteChallenges();
+ Task GetOrder(string[] hostnames);
+ Task<((X509Certificate2 Cert, RSA PrivateKey)?, IDomainResult)> GetCertificate(string subject);
+}
- RegistrationCache? GetRegistrationCache();
+
+
+
+public class LetsEncryptService : ILetsEncryptService {
+
+ //private static readonly JsonSerializerSettings jsonSettings = new JsonSerializerSettings {
+ // NullValueHandling = NullValueHandling.Ignore,
+ // Formatting = Formatting.Indented
+ //};
+
+ private readonly ILogger _logger;
+
+ private HttpClient _httpClient;
+
+ private IJwsService? _jwsService;
+ private AcmeDirectory? _directory;
+ private RegistrationCache? _cache;
+
+ private string? _nonce;
+
+ private List _challenges = new List();
+ private Order? _currentOrder;
+
+ public LetsEncryptService(
+ ILogger logger,
+ HttpClient httpClient
+ ) {
+ _logger = logger;
+ _httpClient = httpClient;
}
-
-
- public class LetsEncryptService : ILetsEncryptService {
-
- //private static readonly JsonSerializerSettings jsonSettings = new JsonSerializerSettings {
- // NullValueHandling = NullValueHandling.Ignore,
- // Formatting = Formatting.Indented
- //};
-
- private readonly ILogger _logger;
-
- private HttpClient _httpClient;
-
- private IJwsService _jwsService;
- private AcmeDirectory? _directory;
- private RegistrationCache? _cache;
-
- private string? _nonce;
-
- private List _challenges = new List();
- private Order? _currentOrder;
-
- public LetsEncryptService(
- ILogger logger,
- HttpClient httpClient
- ) {
- _logger = logger;
- _httpClient = httpClient;
- }
-
-
- ///
- ///
- ///
- ///
- ///
- ///
- public async Task ConfigureClient(string url) {
-
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task ConfigureClient(string url) {
+ try {
_httpClient.BaseAddress ??= new Uri(url);
- (_directory, _) = await SendAsync(HttpMethod.Get, new Uri("directory", UriKind.Relative), false, null);
- }
+ var (directory, getAcmeDirectoryResult) = await SendAsync(HttpMethod.Get, new Uri("directory", UriKind.Relative), false, null);
+ if (!getAcmeDirectoryResult.IsSuccess)
+ return getAcmeDirectoryResult;
- ///
- /// Account creation or Initialization from cache
- ///
- ///
- ///
- ///
- public async Task Init(string? [] contacts, RegistrationCache? cache) {
+ _directory = directory.Result;
+
+ return IDomainResult.Success();
+ }
+ catch (Exception ex) {
+ _logger.LogError(ex, "Let's Encrypt client unhandled exception");
+ return IDomainResult.CriticalDependencyError();
+ }
+ }
+
+ ///
+ /// Account creation or Initialization from cache
+ ///
+ ///
+ ///
+ ///
+ public async Task Init(string? [] contacts, RegistrationCache? cache) {
+
+ try {
+
+ _logger.LogInformation($"Executing {nameof(Init)}...");
if (contacts == null || contacts.Length == 0)
- throw new ArgumentNullException();
+ return IDomainResult.Failed();
if (_directory == null)
- throw new ArgumentNullException();
+ return IDomainResult.Failed();
var accountKey = new RSACryptoServiceProvider(4096);
@@ -115,58 +129,82 @@ namespace MaksIT.LetsEncrypt.Services {
Contacts = contacts.Select(contact => $"mailto:{contact}").ToArray()
};
- var (account, response) = await SendAsync(HttpMethod.Post, _directory.NewAccount, false, letsEncryptOrder);
- _jwsService.SetKeyId(account.Location.ToString());
+ var (account, postAccuntResult) = await SendAsync(HttpMethod.Post, _directory.NewAccount, false, letsEncryptOrder);
+ _jwsService.SetKeyId(account.Result.Location.ToString());
- if (account.Status != "valid")
- throw new InvalidOperationException($"Account status is not valid, was: {account.Status} \r\n {response}");
+ if (account.Result.Status != "valid") {
+ _logger.LogError($"Account status is not valid, was: {account.Result.Status} \r\n {account.ResponseText}");
+ return IDomainResult.Failed();
+ }
_cache = new RegistrationCache {
- Location = account.Location,
+ Location = account.Result.Location,
AccountKey = accountKey.ExportCspBlob(true),
- Id = account.Id,
- Key = account.Key
+ Id = account.Result.Id,
+ Key = account.Result.Key
};
+
+ return IDomainResult.Success();
}
+ catch (Exception ex) {
+ var message = "Let's Encrypt client unhandled exception";
- ///
- ///
- ///
- ///
- public RegistrationCache? GetRegistrationCache() =>
- _cache;
-
- ///
- /// Just retrive terms of service
- ///
- ///
- ///
- public string GetTermsOfServiceUri() {
-
- if (_directory == null)
- throw new NullReferenceException();
-
- return _directory.Meta.TermsOfService;
+ _logger.LogError(ex, message);
+ return IDomainResult.CriticalDependencyError(message);
}
+ }
+ ///
+ ///
+ ///
+ ///
+ public RegistrationCache? GetRegistrationCache() =>
+ _cache;
+ ///
+ /// Just retrive terms of service
+ ///
+ ///
+ ///
+ public (string?, IDomainResult) GetTermsOfServiceUri() {
+ try {
+
+ _logger.LogInformation($"Executing {nameof(GetTermsOfServiceUri)}...");
+
+ if (_directory == null) {
+ return IDomainResult.Failed();
+ }
+
+ return IDomainResult.Success(_directory.Meta.TermsOfService);
+ }
+ catch (Exception ex) {
+ var message = "Let's Encrypt client unhandled exception";
+
+ _logger.LogError(ex, message);
+ return IDomainResult.CriticalDependencyError(message);
+ }
+ }
+
+ ///
+ /// Create new Certificate Order. In case you want the wildcard-certificate you must select dns-01 challange.
+ ///
+ /// Available challange types:
+ ///
+ /// - dns-01
+ /// - http-01
+ /// - tls-alpn-01
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task<(Dictionary?, IDomainResult)> NewOrder(string[] hostnames, string challengeType) {
+ try {
+
+ _logger.LogInformation($"Executing {nameof(NewOrder)}...");
- ///
- /// Create new Certificate Order. In case you want the wildcard-certificate you must select dns-01 challange.
- ///
- /// Available challange types:
- ///
- /// - dns-01
- /// - http-01
- /// - tls-alpn-01
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public async Task> NewOrder(string[] hostnames, string challengeType) {
_challenges.Clear();
var letsEncryptOrder = new Order {
@@ -177,27 +215,38 @@ namespace MaksIT.LetsEncrypt.Services {
}).ToArray()
};
- var (order, response) = await SendAsync(HttpMethod.Post, _directory.NewOrder, false, letsEncryptOrder);
+ var (order, postNewOrderResult) = await SendAsync(HttpMethod.Post, _directory.NewOrder, false, letsEncryptOrder);
+ if (!postNewOrderResult.IsSuccess) {
+ return (null, postNewOrderResult);
+ }
- if (order.Status == "ready")
- return new Dictionary();
+ if (order.Result.Status == "ready")
+ return IDomainResult.Success(new Dictionary());
- if (order.Status != "pending")
- throw new InvalidOperationException($"Created new order and expected status 'pending', but got: {order.Status} \r\n {response}");
-
- _currentOrder = order;
+ if (order.Result.Status != "pending") {
+ _logger.LogError($"Created new order and expected status 'pending', but got: {order.Result.Status} \r\n {order.Result}");
+ return IDomainResult.Failed?>();
+ }
+
+ _currentOrder = order.Result;
var results = new Dictionary();
- foreach (var item in order.Authorizations) {
- var (challengeResponse, responseText) = await SendAsync(HttpMethod.Post, item, true, null);
+ foreach (var item in order.Result.Authorizations) {
- if (challengeResponse.Status == "valid")
+ var (challengeResponse, postAuthorisationChallengeResult) = await SendAsync(HttpMethod.Post, item, true, null);
+ if (!postAuthorisationChallengeResult.IsSuccess) {
+ return (null, postAuthorisationChallengeResult);
+ }
+
+ if (challengeResponse.Result.Status == "valid")
continue;
- if (challengeResponse.Status != "pending")
- throw new InvalidOperationException($"Expected autorization status 'pending', but got: {order.Status} \r\n {responseText}");
+ if (challengeResponse.Result.Status != "pending") {
+ _logger.LogError($"Expected autorization status 'pending', but got: {order.Result.Status} \r\n {challengeResponse.ResponseText}");
+ return IDomainResult.Failed?>();
+ }
- var challenge = challengeResponse.Challenges.First(x => x.Type == challengeType);
+ var challenge = challengeResponse.Result.Challenges.First(x => x.Type == challengeType);
_challenges.Add(challenge);
var keyToken = _jwsService.GetKeyAuthorization(challenge.Token);
@@ -215,7 +264,7 @@ namespace MaksIT.LetsEncrypt.Services {
case "dns-01": {
using (var sha256 = SHA256.Create()) {
var dnsToken = _jwsService.Base64UrlEncoded(sha256.ComputeHash(Encoding.UTF8.GetBytes(keyToken)));
- results[challengeResponse.Identifier.Value] = dnsToken;
+ results[challengeResponse.Result.Identifier.Value] = dnsToken;
}
break;
}
@@ -232,7 +281,7 @@ namespace MaksIT.LetsEncrypt.Services {
// representation of the key authorization.
case "http-01": {
- results[challengeResponse.Identifier.Value] = keyToken;
+ results[challengeResponse.Result.Identifier.Value] = keyToken;
break;
}
@@ -241,22 +290,38 @@ namespace MaksIT.LetsEncrypt.Services {
}
}
- return results;
+ return IDomainResult.Success(results);
}
+ catch (Exception ex) {
+ var message = "Let's Encrypt client unhandled exception";
- ///
- ///
- ///
- ///
- ///
- public async Task CompleteChallenges() {
+ _logger.LogError(ex, message);
+ return IDomainResult.CriticalDependencyError?>(message);
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task CompleteChallenges() {
+ try {
+
+ _logger.LogInformation($"Executing {nameof(CompleteChallenges)}...");
+
+ if (_currentOrder?.Identifiers == null) {
+ return IDomainResult.Failed();
+ }
for (var index = 0; index < _challenges.Count; index++) {
var challenge = _challenges[index];
+ var start = DateTime.UtcNow;
+
while (true) {
- AuthorizeChallenge authorizeChallenge = new AuthorizeChallenge();
+ var authorizeChallenge = new AuthorizeChallenge();
switch (challenge.Type) {
case "dns-01": {
@@ -270,24 +335,46 @@ namespace MaksIT.LetsEncrypt.Services {
}
}
- var (result, responseText) = await SendAsync(HttpMethod.Post, challenge.Url, false, "{}");
+ var (authChallenge, postAuthChallengeResult) = await SendAsync(HttpMethod.Post, challenge.Url, false, "{}");
+ if (!postAuthChallengeResult.IsSuccess) {
+ return postAuthChallengeResult;
+ }
- if (result.Status == "valid")
+ if (authChallenge.Result.Status == "valid")
break;
- if (result.Status != "pending")
- throw new InvalidOperationException($"Failed autorization of {_currentOrder.Identifiers[index].Value} \r\n {responseText}");
+
+ if (authChallenge.Result.Status != "pending") {
+ _logger.LogError($"Failed autorization of {_currentOrder.Identifiers[index].Value} \r\n {authChallenge.ResponseText}");
+ return IDomainResult.Failed();
+ }
await Task.Delay(1000);
+
+ if ((DateTime.UtcNow - start).Seconds > 120)
+ throw new TimeoutException();
}
}
- }
- ///
- ///
- ///
- ///
- ///
- public async Task GetOrder(string[] hostnames) {
+ return IDomainResult.Success();
+ }
+ catch (Exception ex) {
+ var message = "Let's Encrypt client unhandled exception";
+
+ _logger.LogError(ex, message);
+ return IDomainResult.CriticalDependencyError(message);
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task GetOrder(string[] hostnames) {
+
+ try {
+
+ _logger.LogInformation($"Executing {nameof(GetOrder)}");
var letsEncryptOrder = new Order {
Expires = DateTime.UtcNow.AddDays(2),
@@ -297,24 +384,36 @@ namespace MaksIT.LetsEncrypt.Services {
}).ToArray()
};
- var (order, response) = await SendAsync(HttpMethod.Post, _directory.NewOrder, false, letsEncryptOrder);
+ var (order, postOrderResult) = await SendAsync(HttpMethod.Post, _directory.NewOrder, false, letsEncryptOrder);
+ if (!postOrderResult.IsSuccess)
+ return postOrderResult;
- _currentOrder = order;
+ _currentOrder = order.Result;
+
+ return IDomainResult.Success();
}
+ catch (Exception ex) {
+ var message = "Let's Encrypt client unhandled exception";
- ///
- ///
- ///
- ///
- /// Cert and Private key
- ///
- public async Task<(X509Certificate2 Cert, RSA PrivateKey)> GetCertificate(string subject) {
+ _logger.LogError(ex, message);
+ return IDomainResult.CriticalDependencyError(message);
+ }
+ }
- _logger.LogInformation($"Invoked: {nameof(GetCertificate)}");
+ ///
+ ///
+ ///
+ ///
+ /// Cert and Private key
+ ///
+ public async Task<((X509Certificate2 Cert, RSA PrivateKey)?, IDomainResult)> GetCertificate(string subject) {
+ try {
+ _logger.LogInformation($"Executing {nameof(GetCertificate)}...");
- if (_currentOrder == null)
- throw new ArgumentNullException();
+ if (_currentOrder == null) {
+ return IDomainResult.Failed<(X509Certificate2 Cert, RSA PrivateKey)?>();
+ }
var key = new RSACryptoServiceProvider(4096);
var csr = new CertificateRequest("CN=" + subject,
@@ -340,76 +439,135 @@ namespace MaksIT.LetsEncrypt.Services {
await GetOrder(_currentOrder.Identifiers.Select(x => x.Value).ToArray());
if (_currentOrder.Status == "ready") {
- var (response, responseText) = await SendAsync(HttpMethod.Post, _currentOrder.Finalize, false, letsEncryptOrder);
+ var (order, postOrderResult) = await SendAsync(HttpMethod.Post, _currentOrder.Finalize, false, letsEncryptOrder);
+ if (!postOrderResult.IsSuccess || order?.Result == null)
+ return (null, postOrderResult);
- if (response.Status == "processing")
- (response, responseText) = await SendAsync(HttpMethod.Post, _currentOrder.Location, true, null);
- if (response.Status == "valid") {
- certificateUrl = response.Certificate;
+ if (order.Result.Status == "processing") {
+ (order, postOrderResult) = await SendAsync(HttpMethod.Post, _currentOrder.Location, true, null);
+ if (!postOrderResult.IsSuccess || order?.Result == null)
+ return (null, postOrderResult);
+ }
+
+ if (order.Result.Status == "valid") {
+ certificateUrl = order.Result.Certificate;
}
}
- if ((start - DateTime.UtcNow).Seconds > 120)
+ if ((DateTime.UtcNow - start).Seconds > 120)
throw new TimeoutException();
await Task.Delay(1000);
- continue;
-
- // throw new InvalidOperationException(/*$"Invalid order status: "*/);
}
- var (pem, _) = await SendAsync(HttpMethod.Post, certificateUrl, true, null);
+ var (pem, postPemResult) = await SendAsync(HttpMethod.Post, certificateUrl, true, null);
+ if (!postPemResult.IsSuccess || pem?.Result == null)
+ return (null, postPemResult);
- if (_cache == null)
- throw new NullReferenceException();
+
+ if (_cache == null) {
+ _logger.LogError($"{nameof(_cache)} is null");
+ return IDomainResult.Failed<(X509Certificate2 Cert, RSA PrivateKey)?>();
+ }
_cache.CachedCerts ??= new Dictionary();
_cache.CachedCerts[subject] = new CertificateCache {
- Cert = pem,
+ Cert = pem.Result,
Private = key.ExportCspBlob(true)
};
- var cert = new X509Certificate2(Encoding.UTF8.GetBytes(pem));
+ var cert = new X509Certificate2(Encoding.UTF8.GetBytes(pem.Result));
- return (cert, key);
+ return IDomainResult.Success((cert, key));
}
+ catch (Exception ex) {
+ var message = "Let's Encrypt client unhandled exception";
- ///
- ///
- ///
- ///
- ///
- public Task KeyChange() {
- throw new NotImplementedException();
+ _logger.LogError(ex, message);
+ return IDomainResult.CriticalDependencyError< (X509Certificate2 Cert, RSA PrivateKey)?>(message);
}
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public Task KeyChange() {
+ throw new NotImplementedException();
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public Task RevokeCertificate() {
+ throw new NotImplementedException();
+ }
+
+
+ ///
+ /// Request New Nonce to be able to start POST requests
+ ///
+ ///
+ ///
+ private async Task<(string?, IDomainResult)> NewNonce() {
+
+ try {
+
+ _logger.LogInformation($"Executing {nameof(NewNonce)}...");
+
+ if (_directory == null)
+ IDomainResult.Failed();
+
+ var result = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, _directory.NewNonce));
+ return IDomainResult.Success(result.Headers.GetValues("Replay-Nonce").First());
- ///
- ///
- ///
- ///
- ///
- public Task RevokeCertificate() {
- throw new NotImplementedException();
}
+ catch (Exception ex) {
+ var message = "Let's Encrypt client unhandled exception";
+
+ _logger.LogError(ex, message);
+ return IDomainResult.CriticalDependencyError(message);
+ }
+ }
+
+ ///
+ /// Main method used to send data to LetsEncrypt
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private async Task<(SendResult?, IDomainResult)> SendAsync(HttpMethod method, Uri uri, bool isPostAsGet, object? requestModel) {
+ try {
+
+ _logger.LogInformation($"Executing {nameof(SendAsync)}...");
+
+ //if (_jwsService == null) {
+ // _logger.LogError($"{nameof(_jwsService)} is null");
+ // return IDomainResult.Failed?>();
+ //}
- ///
- /// Main method used to send data to LetsEncrypt
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- private async Task<(TResult, string)> SendAsync(HttpMethod method, Uri uri, bool isPostAsGet, object? message) where TResult : class {
var request = new HttpRequestMessage(method, uri);
- _nonce = uri.OriginalString != "directory"
- ? await NewNonce()
- : default;
+ if (uri.OriginalString != "directory") {
+ var (nonce, newNonceResult) = await NewNonce();
+ if (!newNonceResult.IsSuccess || nonce == null) {
+ return (null, newNonceResult);
+ }
- if (message != null || isPostAsGet) {
+ _nonce = nonce;
+ }
+ else {
+ _nonce = default;
+ }
+
+ if (requestModel != null || isPostAsGet) {
var jwsHeader = new JwsHeader {
Url = uri,
};
@@ -419,7 +577,7 @@ namespace MaksIT.LetsEncrypt.Services {
var encodedMessage = isPostAsGet
? _jwsService.Encode(jwsHeader)
- : _jwsService.Encode(message, jwsHeader);
+ : _jwsService.Encode(requestModel, jwsHeader);
var json = encodedMessage.ToJson();
@@ -438,17 +596,15 @@ namespace MaksIT.LetsEncrypt.Services {
if (method == HttpMethod.Post)
_nonce = response.Headers.GetValues("Replay-Nonce").First();
- if (response.Content.Headers.ContentType.MediaType == "application/problem+json") {
- var problemJson = await response.Content.ReadAsStringAsync();
- var problem = problemJson.ToObject();
- problem.RawJson = problemJson;
- throw new LetsEncrytException(problem, response);
- }
-
var responseText = await response.Content.ReadAsStringAsync();
- if (typeof(TResult) == typeof(string) && response.Content.Headers.ContentType.MediaType == "application/pem-certificate-chain") {
- return ((TResult)(object)responseText, null);
+ if (response.Content.Headers.ContentType?.MediaType == "application/problem+json")
+ throw new LetsEncrytException(responseText.ToObject(), response);
+
+ if (response.Content.Headers.ContentType?.MediaType == "application/pem-certificate-chain" && typeof(TResult) == typeof(string)) {
+ return IDomainResult.Success(new SendResult {
+ Result = (TResult)(object)responseText
+ });
}
var responseContent = responseText.ToObject();
@@ -458,20 +614,17 @@ namespace MaksIT.LetsEncrypt.Services {
ihl.Location = response.Headers.Location;
}
- return (responseContent, responseText);
+ return IDomainResult.Success(new SendResult {
+ Result = responseContent,
+ ResponseText = responseText
+ });
+
}
+ catch (Exception ex) {
+ var message = "Let's Encrypt client unhandled exception";
- ///
- /// Request New Nonce to be able to start POST requests
- ///
- ///
- ///
- private async Task NewNonce() {
- if (_directory == null)
- throw new NotImplementedException();
-
- var result = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, _directory.NewNonce));
- return result.Headers.GetValues("Replay-Nonce").First();
+ _logger.LogError(ex, message);
+ return IDomainResult.CriticalDependencyError?>(message);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/LetsEncryptConsole/App.cs b/src/LetsEncryptConsole/App.cs
index a2a021e..9d12d13 100644
--- a/src/LetsEncryptConsole/App.cs
+++ b/src/LetsEncryptConsole/App.cs
@@ -1,7 +1,3 @@
-using System.Text;
-using System.Security.Cryptography;
-using System.Security.Cryptography.X509Certificates;
-
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -10,314 +6,257 @@ using MaksIT.Core.Extensions;
using MaksIT.LetsEncrypt.Services;
using MaksIT.LetsEncrypt.Entities;
using MaksIT.LetsEncryptConsole.Services;
-using SSHProvider;
-using Mono.Unix.Native;
-using Serilog.Core;
-namespace MaksIT.LetsEncryptConsole {
+using MaksIT.SSHProvider;
- public interface IApp {
+namespace MaksIT.LetsEncryptConsole;
- Task Run(string[] args);
+public interface IApp {
+
+ Task Run(string[] args);
+}
+
+public class App : IApp {
+
+ private readonly string _appPath = AppDomain.CurrentDomain.BaseDirectory;
+
+ private readonly ILogger _logger;
+ private readonly Configuration _appSettings;
+ private readonly ILetsEncryptService _letsEncryptService;
+ private readonly ITerminalService _terminalService;
+
+ public App(
+ ILogger logger,
+ IOptions appSettings,
+ ILetsEncryptService letsEncryptService,
+ ITerminalService terminalService
+ ) {
+ _logger = logger;
+ _appSettings = appSettings.Value;
+ _letsEncryptService = letsEncryptService;
+ _terminalService = terminalService;
}
- public class App : IApp {
+ public async Task Run(string[] args) {
- private readonly string _appPath = AppDomain.CurrentDomain.BaseDirectory;
-
- private readonly ILogger _logger;
- private readonly Configuration _appSettings;
- private readonly ILetsEncryptService _letsEncryptService;
- private readonly IKeyService _keyService;
- private readonly ITerminalService _terminalService;
-
- public App(
- ILogger logger,
- IOptions appSettings,
- ILetsEncryptService letsEncryptService,
- IKeyService keyService,
- ITerminalService terminalService
- ) {
- _logger = logger;
- _appSettings = appSettings.Value;
- _letsEncryptService = letsEncryptService;
- _keyService = keyService;
- _terminalService = terminalService;
- }
-
- public async Task Run(string[] args) {
-
- _logger.LogInformation("Letsencrypt client estarted...");
+ try {
+ _logger.LogInformation("Let's Encrypt client. Started...");
foreach (var env in _appSettings.Environments?.Where(x => x.Active) ?? new List()) {
- try {
- _logger.LogInformation($"Let's Encrypt C# .Net Core Client, environment: {env.Name}");
- //loop all customers
- foreach (Customer customer in _appSettings.Customers?.Where(x => x.Active) ?? new List()) {
- try {
- _logger.LogInformation($"Managing customer: {customer.Id} - {customer.Name} {customer.LastName}");
+ _logger.LogInformation($"Let's Encrypt C# .Net Core Client, environment: {env.Name}");
- //define cache folder
- string cachePath = Path.Combine(_appPath, customer.Id, env.Name, "cache");
- if (!Directory.Exists(cachePath)) {
- Directory.CreateDirectory(cachePath);
+ //loop all customers
+ foreach (Customer customer in _appSettings.Customers?.Where(x => x.Active) ?? new List()) {
+
+ _logger.LogInformation($"Managing customer: {customer.Id} - {customer.Name} {customer.LastName}");
+
+ //define cache folder
+ string cachePath = Path.Combine(_appPath, customer.Id, env.Name, "cache");
+ if (!Directory.Exists(cachePath)) {
+ Directory.CreateDirectory(cachePath);
+ }
+
+ //check acme directory
+ var acmePath = Path.Combine(_appPath, customer.Id, env.Name, "acme");
+ if (!Directory.Exists(acmePath)) {
+ Directory.CreateDirectory(acmePath);
+ }
+
+ //loop each customer website
+ foreach (Site site in customer.Sites?.Where(s => s.Active) ?? new List()) {
+ _logger.LogInformation($"Managing site: {site.Name}");
+
+
+ //create folder for ssl
+ string sslPath = Path.Combine(_appPath, customer.Id, env.Name, "ssl", site.Name);
+ if (!Directory.Exists(sslPath)) {
+ Directory.CreateDirectory(sslPath);
+ }
+
+ var cacheFile = Path.Combine(cachePath, $"{site.Name}.lets-encrypt.cache.json");
+
+
+
+ #region LetsEncrypt client configuration and local registration cache initialization
+ _logger.LogInformation("1. Client Initialization...");
+
+ await _letsEncryptService.ConfigureClient(env.Url);
+
+ var registrationCache = (File.Exists(cacheFile)
+ ? File.ReadAllText(cacheFile)
+ : null)
+ .ToObject();
+
+ var initResult = await _letsEncryptService.Init(customer.Contacts, registrationCache);
+ if (!initResult.IsSuccess) {
+ continue;
+ }
+ #endregion
+
+ #region LetsEncrypt terms of service
+ _logger.LogInformation($"Terms of service: {_letsEncryptService.GetTermsOfServiceUri()}");
+ #endregion
+
+ // get cached certificate and check if it's valid
+ // if valid check if cert and key exists otherwise recreate
+ // else continue with new certificate request
+ var certRes = new CachedCertificateResult();
+ if (registrationCache != null && registrationCache.TryGetCachedCertificate(site.Name, out certRes)) {
+
+ File.WriteAllText(Path.Combine(sslPath, $"{site.Name}.crt"), certRes.Certificate);
+
+ if (certRes.PrivateKey != null)
+ File.WriteAllText(Path.Combine(sslPath, $"{site.Name}.key"), certRes.PrivateKey.ExportRSAPrivateKeyPem());
+
+ _logger.LogInformation("Certificate and Key exists and valid. Restored from cache.");
+ }
+ else {
+
+
+ //create new orders
+ #region LetsEncrypt new order
+ _logger.LogInformation("2. Client New Order...");
+
+ var (orders, newOrderResult) = await _letsEncryptService.NewOrder(site.Hosts, site.Challenge);
+ if (!newOrderResult.IsSuccess || orders == null) {
+ continue;
+ }
+ #endregion
+
+ if (orders.Count > 0) {
+ switch (site.Challenge) {
+ case "http-01": {
+ //ensure to enable static file discovery on server in .well-known/acme-challenge
+ //and listen on 80 port
+
+ foreach (FileInfo file in new DirectoryInfo(acmePath).GetFiles())
+ file.Delete();
+
+ foreach (var result in orders) {
+ Console.WriteLine($"Key: {result.Key}, Value: {result.Value}");
+ string[] splitToken = result.Value.Split('.');
+
+ File.WriteAllText(Path.Combine(acmePath, splitToken[0]), result.Value);
+ }
+
+ foreach (FileInfo file in new DirectoryInfo(acmePath).GetFiles()) {
+ if (env?.SSH?.Active ?? false) {
+ UploadFiles(_logger, env.SSH, env.ACME.Linux.Path, file.Name, File.ReadAllBytes(file.FullName), env.ACME.Linux.Owner, env.ACME.Linux.ChangeMode);
+ }
+ else {
+ throw new NotImplementedException();
+ }
+ }
+
+ break;
+ }
+
+ case "dns-01": {
+ //Manage DNS server MX record, depends from provider
+ throw new NotImplementedException();
+ }
+
+ default: {
+ throw new NotImplementedException();
+ }
+ }
+
+
+ #region LetsEncrypt complete challenges
+ _logger.LogInformation("3. Client Complete Challange...");
+ var completeChallengesResult = await _letsEncryptService.CompleteChallenges();
+ if (!completeChallengesResult.IsSuccess) {
+ continue;
+ }
+ _logger.LogInformation("Challanges comleted.");
+ #endregion
+
+ await Task.Delay(1000);
+
+ #region Download new certificate
+ _logger.LogInformation("4. Download certificate...");
+ var (certData, getCertResult) = await _letsEncryptService.GetCertificate(site.Name);
+ if (!getCertResult.IsSuccess || certData == null) {
+ continue;
+ }
+
+ // not used in this scenario
+ // var (cert, key) = certData.Value;
+ #endregion
+
+ #region Persist cache
+ registrationCache = _letsEncryptService.GetRegistrationCache();
+ File.WriteAllText(cacheFile, registrationCache.ToJson());
+ #endregion
}
- //check acme directory
- var acmePath = Path.Combine(_appPath, customer.Id, env.Name, "acme");
- if (!Directory.Exists(acmePath)) {
- Directory.CreateDirectory(acmePath);
- }
+ #region Save cert and key to filesystem
+ certRes = new CachedCertificateResult();
+ if (registrationCache.TryGetCachedCertificate(site.Name, out certRes)) {
- //loop each customer website
- foreach (Site site in customer.Sites?.Where(s => s.Active) ?? new List()) {
- _logger.LogInformation($"Managing site: {site.Name}");
+ File.WriteAllText(Path.Combine(sslPath, $"{site.Name}.crt"), certRes.Certificate);
- try {
- //create folder for ssl
- string sslPath = Path.Combine(_appPath, customer.Id, env.Name, "ssl", site.Name);
- if (!Directory.Exists(sslPath)) {
- Directory.CreateDirectory(sslPath);
- }
+ if (certRes.PrivateKey != null)
+ File.WriteAllText(Path.Combine(sslPath, $"{site.Name}.key"), certRes.PrivateKey.ExportRSAPrivateKeyPem());
- var cacheFile = Path.Combine(cachePath, $"{site.Name}.lets-encrypt.cache.json");
+ _logger.LogInformation("Certificate saved.");
- //1. Client initialization
- _logger.LogInformation("1. Client Initialization...");
+ foreach (FileInfo file in new DirectoryInfo(sslPath).GetFiles()) {
- #region LetsEncrypt client configuration
- await _letsEncryptService.ConfigureClient(env.Url);
- #endregion
-
- #region LetsEncrypt local registration cache initialization
- var registrationCache = (File.Exists(cacheFile)
- ? File.ReadAllText(cacheFile)
- : null)
- .ToObject();
-
- await _letsEncryptService.Init(customer.Contacts, registrationCache);
- #endregion
-
- #region LetsEncrypt terms of service
- _logger.LogInformation($"Terms of service: {_letsEncryptService.GetTermsOfServiceUri()}");
- #endregion
-
- // get cached certificate and check if it's valid
- // if valid check if cert and key exists otherwise recreate
- // else continue with new certificate request
- var certRes = new CachedCertificateResult();
- if (TryGetCachedCertificate(registrationCache, site.Name, out certRes)) {
- string cert = Path.Combine(sslPath, $"{site.Name}.crt");
- //if(!File.Exists(cert))
- File.WriteAllText(cert, certRes.Certificate);
-
- string key = Path.Combine(sslPath, $"{site.Name}.key");
- //if(!File.Exists(key)) {
- using (StreamWriter writer = File.CreateText(key))
- _keyService.ExportPrivateKey(certRes.PrivateKey, writer);
- //}
-
- _logger.LogInformation("Certificate and Key exists and valid. Restored from cache.");
+ if (env?.SSH?.Active ?? false) {
+ UploadFiles(_logger, env.SSH, $"{env.SSL.Linux.Path}/{site.Name}", file.Name, File.ReadAllBytes(file.FullName), env.SSL.Linux.Owner, env.SSL.Linux.ChangeMode);
}
else {
-
- //try to make new order
- try {
- //create new orders
- Console.WriteLine("2. Client New Order...");
-
- #region LetsEncrypt new order
- var orders = await _letsEncryptService.NewOrder(site.Hosts, site.Challenge);
- #endregion
-
- if (orders.Count > 0) {
- switch (site.Challenge) {
- case "http-01": {
- //ensure to enable static file discovery on server in .well-known/acme-challenge
- //and listen on 80 port
-
- foreach (FileInfo file in new DirectoryInfo(acmePath).GetFiles())
- file.Delete();
-
- foreach (var result in orders) {
- Console.WriteLine($"Key: {result.Key}, Value: {result.Value}");
- string[] splitToken = result.Value.Split('.');
-
- File.WriteAllText(Path.Combine(acmePath, splitToken[0]), result.Value);
- }
-
- foreach (FileInfo file in new DirectoryInfo(acmePath).GetFiles()) {
- if (env?.SSH?.Active ?? false) {
- UploadFiles(_logger, env.SSH, env.ACME.Linux.Path, file.Name, File.ReadAllBytes(file.FullName), env.ACME.Linux.Owner, env.ACME.Linux.ChangeMode);
- }
- else {
- throw new NotImplementedException();
- }
- }
-
-
-
-
-
- break;
- }
-
- case "dns-01": {
- //Manage DNS server MX record, depends from provider
- throw new NotImplementedException();
- }
-
- default: {
- throw new NotImplementedException();
- }
- }
-
-
- #region LetsEncrypt complete challenges
- _logger.LogInformation("3. Client Complete Challange...");
- await _letsEncryptService.CompleteChallenges();
- _logger.LogInformation("Challanges comleted.");
- #endregion
-
- await Task.Delay(1000);
-
- // Download new certificate
- _logger.LogInformation("4. Download certificate...");
- var (cert, key) = await _letsEncryptService.GetCertificate(site.Name);
-
- #region Persist cache
- registrationCache = _letsEncryptService.GetRegistrationCache();
- File.WriteAllText(cacheFile, registrationCache.ToJson());
- #endregion
- }
-
- #region Save cert and key to filesystem
- certRes = new CachedCertificateResult();
- if (TryGetCachedCertificate(registrationCache, site.Name, out certRes)) {
-
- File.WriteAllText(Path.Combine(sslPath, site.Name + ".crt"), certRes.Certificate);
-
- using (var writer = File.CreateText(Path.Combine(sslPath, site.Name + ".key"))) {
- _keyService.ExportPrivateKey(certRes.PrivateKey, writer);
- }
-
- _logger.LogInformation("Certificate saved.");
-
- foreach (FileInfo file in new DirectoryInfo(sslPath).GetFiles()) {
-
- if (env?.SSH?.Active ?? false) {
- UploadFiles(_logger, env.SSH, $"{env.SSL.Linux.Path}/{site.Name}", file.Name, File.ReadAllBytes(file.FullName), env.SSL.Linux.Owner, env.SSL.Linux.ChangeMode);
- }
- else {
- throw new NotImplementedException();
- }
- }
-
- }
- else {
- _logger.LogError("Unable to get new cached certificate.");
- }
- #endregion
- }
- catch (Exception ex) {
- _logger.LogError(ex, "");
- await _letsEncryptService.GetOrder(site.Hosts);
- }
-
+ throw new NotImplementedException();
}
-
-
-
-
- }
- catch (Exception ex) {
- _logger.LogError(ex, "Customer unhandled error");
}
+
}
- }
- catch (Exception ex) {
- _logger.LogError(ex, "Environment unhandled error");
+ else {
+ _logger.LogError("Unable to get new cached certificate.");
+ }
+ #endregion
+
}
}
-
- if (env.Name == "ProductionV2") {
- _terminalService.Exec("systemctl restart nginx");
- }
- }
- catch (Exception ex) {
- _logger.LogError(ex.Message.ToString());
- break;
}
}
+
+ _logger.LogInformation($"Let's Encrypt client. Execution complete.");
}
-
- ///
- ///
- ///
- ///
- ///
- ///
- private bool TryGetCachedCertificate(RegistrationCache? registrationCache, string subject, out CachedCertificateResult? value) {
- value = null;
-
- if (registrationCache?.CachedCerts == null)
- return false;
-
- if (!registrationCache.CachedCerts.TryGetValue(subject, out var cache)) {
- return false;
- }
-
- var cert = new X509Certificate2(Encoding.ASCII.GetBytes(cache.Cert));
-
- // if it is about to expire, we need to refresh
- if ((cert.NotAfter - DateTime.UtcNow).TotalDays < 30)
- return false;
-
- var rsa = new RSACryptoServiceProvider(4096);
- rsa.ImportCspBlob(cache.Private);
-
- value = new CachedCertificateResult {
- Certificate = cache.Cert,
- PrivateKey = rsa
- };
- return true;
+ catch (Exception ex) {
+ _logger.LogError(ex, $"Let's Encrypt client. Unhandled exception.");
}
- ///
- ///
- ///
- ///
- public RegistrationCache? ResetCachedCertificate(RegistrationCache? registrationCache, IEnumerable hostsToRemove) {
- if (registrationCache?.CachedCerts != null)
- foreach (var host in hostsToRemove)
- registrationCache.CachedCerts.Remove(host);
+ }
- return registrationCache;
- }
- private void UploadFiles(
- ILogger logger,
- SSHClientSettings sshSettings,
- string workDir,
- string fileName,
- byte [] bytes,
- string owner,
- string changeMode
- ) {
- using var sshService = new SSHService(logger, sshSettings.Host, sshSettings.Port, sshSettings.Username, sshSettings.Password);
- sshService.Connect();
- sshService.RunSudoCommand(sshSettings.Password, $"mkdir {workDir}");
- sshService.RunSudoCommand(sshSettings.Password, $"chown {owner} {workDir} -R");
- sshService.RunSudoCommand(sshSettings.Password, $"chmod 777 {workDir} -R");
+ private void UploadFiles(
+ ILogger logger,
+ SSHClientSettings sshSettings,
+ string workDir,
+ string fileName,
+ byte[] bytes,
+ string owner,
+ string changeMode
+ ) {
+ using var sshService = new SSHService(logger, sshSettings.Host, sshSettings.Port, sshSettings.Username, sshSettings.Password);
+ sshService.Connect();
- sshService.Upload($"{workDir}", fileName, bytes);
+ sshService.RunSudoCommand(sshSettings.Password, $"mkdir {workDir}");
- sshService.RunSudoCommand(sshSettings.Password, $"chown {owner} {workDir} -R");
- sshService.RunSudoCommand(sshSettings.Password, $"chmod {changeMode} {workDir} -R");
- }
+ sshService.RunSudoCommand(sshSettings.Password, $"chown {owner} {workDir} -R");
+ sshService.RunSudoCommand(sshSettings.Password, $"chmod 777 {workDir} -R");
+
+ sshService.Upload($"{workDir}", fileName, bytes);
+
+ sshService.RunSudoCommand(sshSettings.Password, $"chown {owner} {workDir} -R");
+ sshService.RunSudoCommand(sshSettings.Password, $"chmod {changeMode} {workDir} -R");
+
+ //sshService.RunSudoCommand(sshSettings.Password, $"systemctl restart nginx");
}
}
diff --git a/src/LetsEncryptConsole/Configuration.cs b/src/LetsEncryptConsole/Configuration.cs
index afec942..58d0cb1 100644
--- a/src/LetsEncryptConsole/Configuration.cs
+++ b/src/LetsEncryptConsole/Configuration.cs
@@ -1,40 +1,40 @@
-using System.Runtime.InteropServices;
-namespace MaksIT.LetsEncryptConsole {
- public class Configuration {
- public LetsEncryptEnvironment[]? Environments { get; set; }
- public Customer[]? Customers { get; set; }
- }
+namespace MaksIT.LetsEncryptConsole;
- public class OsWindows {
- public string? Path { get; set; }
- }
+public class Configuration {
+ public LetsEncryptEnvironment[]? Environments { get; set; }
+ public Customer[]? Customers { get; set; }
+}
- public class OsLinux {
- public string? Path { get; set; }
+public class OsWindows {
+ public string? Path { get; set; }
+}
- public string? Owner { get; set; }
+public class OsLinux {
+ public string? Path { get; set; }
- public string? ChangeMode { get; set; }
+ public string? Owner { get; set; }
- }
+ public string? ChangeMode { get; set; }
- public class OsDependant {
- public OsWindows? Windows { get; set; }
- public OsLinux? Linux { get; set; }
- }
+}
- public class SSHClientSettings {
- public bool Active { get; set; }
+public class OsDependant {
+ public OsWindows? Windows { get; set; }
+ public OsLinux? Linux { get; set; }
+}
- public string? Host { get; set; }
+public class SSHClientSettings {
+ public bool Active { get; set; }
- public int Port { get; set; }
+ public string? Host { get; set; }
- public string? Username { get; set; }
+ public int Port { get; set; }
- public string? Password { get; set; }
- }
+ public string? Username { get; set; }
+
+ public string? Password { get; set; }
+}
@@ -69,4 +69,3 @@ namespace MaksIT.LetsEncryptConsole {
public string[]? Hosts { get; set; }
public string? Challenge { get; set; }
}
-}
diff --git a/src/LetsEncryptConsole/Program.cs b/src/LetsEncryptConsole/Program.cs
index 2b205e2..7edba4d 100644
--- a/src/LetsEncryptConsole/Program.cs
+++ b/src/LetsEncryptConsole/Program.cs
@@ -6,65 +6,64 @@ using Serilog;
using MaksIT.LetsEncryptConsole.Services;
using MaksIT.LetsEncrypt.Extensions;
-namespace MaksIT.LetsEncryptConsole {
- class Program {
- private static readonly IConfiguration _configuration = InitConfig();
+namespace MaksIT.LetsEncryptConsole;
- static void Main(string[] args) {
- // create service collection
- var services = new ServiceCollection();
- ConfigureServices(services);
+class Program {
+ private static readonly IConfiguration _configuration = InitConfig();
- // create service provider
- var serviceProvider = services.BuildServiceProvider();
+ static void Main(string[] args) {
+ // create service collection
+ var services = new ServiceCollection();
+ ConfigureServices(services);
- // entry to run app
+ // create service provider
+ var serviceProvider = services.BuildServiceProvider();
+
+ // entry to run app
#pragma warning disable CS8602 // Dereference of a possibly null reference.
- var app = serviceProvider.GetService();
- app.Run(args).Wait();
+ var app = serviceProvider.GetService();
+ app.Run(args).Wait();
#pragma warning restore CS8602 // Dereference of a possibly null reference.
- }
+ }
- public static void ConfigureServices(IServiceCollection services) {
+ public static void ConfigureServices(IServiceCollection services) {
- var configurationSection = _configuration.GetSection("Configuration");
- services.Configure(configurationSection);
- var appSettings = configurationSection.Get();
+ var configurationSection = _configuration.GetSection("Configuration");
+ services.Configure(configurationSection);
+ var appSettings = configurationSection.Get();
- #region Configure logging
- services.AddLogging(configure => {
- configure.AddSerilog(new LoggerConfiguration()
- .ReadFrom.Configuration(_configuration)
- .CreateLogger());
- });
- #endregion
+ #region Configure logging
+ services.AddLogging(configure => {
+ configure.AddSerilog(new LoggerConfiguration()
+ .ReadFrom.Configuration(_configuration)
+ .CreateLogger());
+ });
+ #endregion
- #region Services
- services.RegisterLetsEncrypt();
+ #region Services
+ services.RegisterLetsEncrypt();
+ services.AddSingleton();
+ #endregion
- services.AddSingleton();
- services.AddSingleton();
- #endregion
+ // add app
+ services.AddSingleton();
+ }
- // add app
- services.AddSingleton();
- }
+ private static IConfiguration InitConfig() {
+ var aspNetCoreEnvironment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
- private static IConfiguration InitConfig() {
- var aspNetCoreEnvironment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
+ var configuration = new ConfigurationBuilder()
+ .SetBasePath(Directory.GetCurrentDirectory())
+ .AddEnvironmentVariables();
- var configuration = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddEnvironmentVariables();
+ if (!string.IsNullOrWhiteSpace(aspNetCoreEnvironment)
+ && new FileInfo(Path.Combine(Directory.GetCurrentDirectory(), $"appsettings.{aspNetCoreEnvironment}.json")).Exists
+ )
+ configuration.AddJsonFile($"appsettings.{aspNetCoreEnvironment}.json", true);
+ else
+ configuration.AddJsonFile($"appsettings.json", true, true);
- if (!string.IsNullOrWhiteSpace(aspNetCoreEnvironment)
- && new FileInfo(Path.Combine(Directory.GetCurrentDirectory(), $"appsettings.{aspNetCoreEnvironment}.json")).Exists
- )
- configuration.AddJsonFile($"appsettings.{aspNetCoreEnvironment}.json", true);
- else
- configuration.AddJsonFile($"appsettings.json", true, true);
-
- return configuration.Build();
- }
+ return configuration.Build();
}
}
+
diff --git a/src/LetsEncryptConsole/Services/KeyService.cs b/src/LetsEncryptConsole/Services/KeyService.cs
deleted file mode 100644
index 26743fc..0000000
--- a/src/LetsEncryptConsole/Services/KeyService.cs
+++ /dev/null
@@ -1,151 +0,0 @@
-using System.Security.Cryptography;
-
-namespace MaksIT.LetsEncryptConsole.Services {
-
- public interface IKeyService {
- void ExportPublicKey(RSACryptoServiceProvider csp, TextWriter outputStream);
- void ExportPrivateKey(RSACryptoServiceProvider csp, TextWriter outputStream);
- }
-
- public class KeyService : IKeyService {
- ///
- /// Export a certificate to a PEM format string
- ///
- /// The certificate to export
- /// A PEM encoded string
- //public static string ExportToPEM(X509Certificate2 cert)
- //{
- // StringBuilder builder = new StringBuilder();
-
- // builder.AppendLine("-----BEGIN CERTIFICATE-----");
- // builder.AppendLine(Convert.ToBase64String(cert.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks));
- // builder.AppendLine("-----END CERTIFICATE-----");
-
- // return builder.ToString();
- //}
- public void ExportPublicKey(RSACryptoServiceProvider csp, TextWriter outputStream) {
- var parameters = csp.ExportParameters(false);
- using (var stream = new MemoryStream()) {
- var writer = new BinaryWriter(stream);
- writer.Write((byte)0x30); // SEQUENCE
- using (var innerStream = new MemoryStream()) {
- var innerWriter = new BinaryWriter(innerStream);
- innerWriter.Write((byte)0x30); // SEQUENCE
- EncodeLength(innerWriter, 13);
- innerWriter.Write((byte)0x06); // OBJECT IDENTIFIER
- var rsaEncryptionOid = new byte[] { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 };
- EncodeLength(innerWriter, rsaEncryptionOid.Length);
- innerWriter.Write(rsaEncryptionOid);
- innerWriter.Write((byte)0x05); // NULL
- EncodeLength(innerWriter, 0);
- innerWriter.Write((byte)0x03); // BIT STRING
- using (var bitStringStream = new MemoryStream()) {
- var bitStringWriter = new BinaryWriter(bitStringStream);
- bitStringWriter.Write((byte)0x00); // # of unused bits
- bitStringWriter.Write((byte)0x30); // SEQUENCE
- using (var paramsStream = new MemoryStream()) {
- var paramsWriter = new BinaryWriter(paramsStream);
- EncodeIntegerBigEndian(paramsWriter, parameters.Modulus); // Modulus
- EncodeIntegerBigEndian(paramsWriter, parameters.Exponent); // Exponent
- var paramsLength = (int)paramsStream.Length;
- EncodeLength(bitStringWriter, paramsLength);
- bitStringWriter.Write(paramsStream.GetBuffer(), 0, paramsLength);
- }
- var bitStringLength = (int)bitStringStream.Length;
- EncodeLength(innerWriter, bitStringLength);
- innerWriter.Write(bitStringStream.GetBuffer(), 0, bitStringLength);
- }
- var length = (int)innerStream.Length;
- EncodeLength(writer, length);
- writer.Write(innerStream.GetBuffer(), 0, length);
- }
-
- var base64 = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length).ToCharArray();
- outputStream.WriteLine("-----BEGIN PUBLIC KEY-----");
- for (var i = 0; i < base64.Length; i += 64) {
- outputStream.WriteLine(base64, i, Math.Min(64, base64.Length - i));
- }
- outputStream.WriteLine("-----END PUBLIC KEY-----");
- }
- }
-
- public void ExportPrivateKey(RSACryptoServiceProvider csp, TextWriter outputStream) {
- if (csp.PublicOnly) throw new ArgumentException("CSP does not contain a private key", "csp");
- var parameters = csp.ExportParameters(true);
- using (var stream = new MemoryStream()) {
- var writer = new BinaryWriter(stream);
- writer.Write((byte)0x30); // SEQUENCE
- using (var innerStream = new MemoryStream()) {
- var innerWriter = new BinaryWriter(innerStream);
- EncodeIntegerBigEndian(innerWriter, new byte[] { 0x00 }); // Version
- EncodeIntegerBigEndian(innerWriter, parameters.Modulus);
- EncodeIntegerBigEndian(innerWriter, parameters.Exponent);
- EncodeIntegerBigEndian(innerWriter, parameters.D);
- EncodeIntegerBigEndian(innerWriter, parameters.P);
- EncodeIntegerBigEndian(innerWriter, parameters.Q);
- EncodeIntegerBigEndian(innerWriter, parameters.DP);
- EncodeIntegerBigEndian(innerWriter, parameters.DQ);
- EncodeIntegerBigEndian(innerWriter, parameters.InverseQ);
- var length = (int)innerStream.Length;
- EncodeLength(writer, length);
- writer.Write(innerStream.GetBuffer(), 0, length);
- }
-
- var base64 = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length).ToCharArray();
- outputStream.WriteLine("-----BEGIN RSA PRIVATE KEY-----");
- // Output as Base64 with lines chopped at 64 characters
- for (var i = 0; i < base64.Length; i += 64) {
- outputStream.WriteLine(base64, i, Math.Min(64, base64.Length - i));
- }
- outputStream.WriteLine("-----END RSA PRIVATE KEY-----");
- }
- }
-
- private void EncodeLength(BinaryWriter stream, int length) {
- if (length < 0) throw new ArgumentOutOfRangeException("length", "Length must be non-negative");
- if (length < 0x80) {
- // Short form
- stream.Write((byte)length);
- }
- else {
- // Long form
- var temp = length;
- var bytesRequired = 0;
- while (temp > 0) {
- temp >>= 8;
- bytesRequired++;
- }
- stream.Write((byte)(bytesRequired | 0x80));
- for (var i = bytesRequired - 1; i >= 0; i--) {
- stream.Write((byte)(length >> (8 * i) & 0xff));
- }
- }
- }
-
- private void EncodeIntegerBigEndian(BinaryWriter stream, byte[] value, bool forceUnsigned = true) {
- stream.Write((byte)0x02); // INTEGER
- var prefixZeros = 0;
- for (var i = 0; i < value.Length; i++) {
- if (value[i] != 0) break;
- prefixZeros++;
- }
- if (value.Length - prefixZeros == 0) {
- EncodeLength(stream, 1);
- stream.Write((byte)0);
- }
- else {
- if (forceUnsigned && value[prefixZeros] > 0x7f) {
- // Add a prefix zero to force unsigned if the MSB is 1
- EncodeLength(stream, value.Length - prefixZeros + 1);
- stream.Write((byte)0);
- }
- else {
- EncodeLength(stream, value.Length - prefixZeros);
- }
- for (var i = prefixZeros; i < value.Length; i++) {
- stream.Write(value[i]);
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/LetsEncryptConsole/Services/TerminalService.cs b/src/LetsEncryptConsole/Services/TerminalService.cs
index f65a119..7607531 100644
--- a/src/LetsEncryptConsole/Services/TerminalService.cs
+++ b/src/LetsEncryptConsole/Services/TerminalService.cs
@@ -1,30 +1,28 @@
using System.Diagnostics;
-namespace MaksIT.LetsEncryptConsole.Services {
+namespace MaksIT.LetsEncryptConsole.Services;
- public interface ITerminalService {
- void Exec(string cmd);
- }
+public interface ITerminalService {
+ void Exec(string cmd);
+}
- public class TerminalService : ITerminalService {
+public class TerminalService : ITerminalService {
- public void Exec(string cmd) {
- var escapedArgs = cmd.Replace("\"", "\\\"");
+ public void Exec(string cmd) {
+ var escapedArgs = cmd.Replace("\"", "\\\"");
- var pc = new Process {
- StartInfo = new ProcessStartInfo {
- RedirectStandardOutput = true,
- UseShellExecute = false,
- CreateNoWindow = true,
- WindowStyle = ProcessWindowStyle.Hidden,
- FileName = "/bin/bash",
- Arguments = $"-c \"{escapedArgs}\""
- }
- };
+ var pc = new Process {
+ StartInfo = new ProcessStartInfo {
+ RedirectStandardOutput = true,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ FileName = "/bin/bash",
+ Arguments = $"-c \"{escapedArgs}\""
+ }
+ };
- pc.Start();
- pc.WaitForExit();
- }
+ pc.Start();
+ pc.WaitForExit();
}
-
-}
\ No newline at end of file
+}
diff --git a/src/SSHProvider/Configuration.cs b/src/SSHProvider/Configuration.cs
index e562e50..7f082ab 100644
--- a/src/SSHProvider/Configuration.cs
+++ b/src/SSHProvider/Configuration.cs
@@ -1,10 +1,5 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+
+namespace MaksIT.SSHProvider;
-namespace SSHProvider {
- public class Configuration {
- }
+public class Configuration {
}
diff --git a/src/SSHProvider/SSHProvider.csproj b/src/SSHProvider/SSHProvider.csproj
index aef8d01..7a8377a 100644
--- a/src/SSHProvider/SSHProvider.csproj
+++ b/src/SSHProvider/SSHProvider.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/src/SSHProvider/SSHService.cs b/src/SSHProvider/SSHService.cs
index 89b61e0..419f4b0 100644
--- a/src/SSHProvider/SSHService.cs
+++ b/src/SSHProvider/SSHService.cs
@@ -1,11 +1,14 @@
-using DomainResults.Common;
+using System.Text;
+using System.Text.RegularExpressions;
+
using Microsoft.Extensions.Logging;
+using DomainResults.Common;
+
using Renci.SshNet;
using Renci.SshNet.Common;
-using System.Text.RegularExpressions;
-namespace SSHProvider {
+namespace MaksIT.SSHProvider {
public interface ISSHService : IDisposable {
IDomainResult Upload(string workingdirectory, string fileName, byte[] bytes);
@@ -22,8 +25,6 @@ namespace SSHProvider {
public readonly SshClient _sshClient;
public readonly SftpClient _sftpClient;
-
-
public SSHService(
ILogger logger,
string host,
@@ -31,11 +32,40 @@ namespace SSHProvider {
string username,
string password
) {
+
+ if(string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
+ throw new ArgumentNullException($"{nameof(username)} or {nameof(password)} is null, empty or white space");
+
_logger = logger;
_sshClient = new SshClient(host, port, username, password);
_sftpClient = new SftpClient(host, port, username, password);
}
+
+ public SSHService(
+ ILogger logger,
+ string host,
+ int port,
+ string username,
+ string [] privateKeys
+ ) {
+
+ if (string.IsNullOrWhiteSpace(username) || privateKeys.Any(x => string.IsNullOrWhiteSpace(x)))
+ throw new ArgumentNullException($"{nameof(username)} or {nameof(privateKeys)} contains key which is null, empty or white space");
+
+ _logger = logger;
+
+ var privateKeyFiles = new List();
+ foreach (var privateKey in privateKeys) {
+ using (var ms = new MemoryStream(Encoding.ASCII.GetBytes(privateKey))) {
+ privateKeyFiles.Add(new PrivateKeyFile(ms));
+ }
+ }
+
+ _sshClient = new SshClient(host, port, username, privateKeyFiles.ToArray());
+ _sftpClient = new SftpClient(host, port, username, privateKeyFiles.ToArray());
+ }
+
public IDomainResult Connect() {
try {
_sshClient.Connect();
@@ -77,8 +107,8 @@ namespace SSHProvider {
_logger.LogInformation($"Listing directory:");
- foreach (var fi in listDirectory) {
- _logger.LogInformation($" - " + fi.Name);
+ foreach (var file in listDirectory) {
+ _logger.LogInformation($" - " + file.Name);
}
return IDomainResult.Success();
diff --git a/src/Tests/SSHSerivceTests/Abstractions/ConfigurationBase.cs b/src/Tests/SSHSerivceTests/Abstractions/ConfigurationBase.cs
index b67b6ca..147f91e 100644
--- a/src/Tests/SSHSerivceTests/Abstractions/ConfigurationBase.cs
+++ b/src/Tests/SSHSerivceTests/Abstractions/ConfigurationBase.cs
@@ -7,57 +7,56 @@ using Xunit;
//using PecMgr.VaultProvider;
//using PecMgr.Core.Abstractions;
-namespace MaksIT.Tests.SSHProviderTests.Abstractions {
- //[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
- public abstract class ConfigurationBase {
+namespace MaksIT.Tests.SSHProviderTests.Abstractions;
+//[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
+public abstract class ConfigurationBase {
- protected IConfiguration Configuration;
+ protected IConfiguration Configuration;
- protected ServiceCollection ServiceCollection = new ServiceCollection();
+ protected ServiceCollection ServiceCollection = new ServiceCollection();
- protected ServiceProvider ServiceProvider { get => ServiceCollection.BuildServiceProvider(); }
+ protected ServiceProvider ServiceProvider { get => ServiceCollection.BuildServiceProvider(); }
- public ConfigurationBase() {
- Configuration = InitConfig();
- ConfigureServices(ServiceCollection);
- }
+ public ConfigurationBase() {
+ Configuration = InitConfig();
+ ConfigureServices(ServiceCollection);
+ }
- protected abstract void ConfigureServices(IServiceCollection services);
+ protected abstract void ConfigureServices(IServiceCollection services);
- private IConfiguration InitConfig() {
- var aspNetCoreEnvironment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
- var currentDirectory = Directory.GetCurrentDirectory();
+ private IConfiguration InitConfig() {
+ var aspNetCoreEnvironment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
+ var currentDirectory = Directory.GetCurrentDirectory();
- var configurationBuilder = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddEnvironmentVariables();
+ var configurationBuilder = new ConfigurationBuilder()
+ .SetBasePath(Directory.GetCurrentDirectory())
+ .AddEnvironmentVariables();
- if (!string.IsNullOrWhiteSpace(aspNetCoreEnvironment) && new FileInfo(Path.Combine(currentDirectory, $"appsettings.{aspNetCoreEnvironment}.json")).Exists)
- configurationBuilder.AddJsonFile($"appsettings.{aspNetCoreEnvironment}.json", true);
- else if (new FileInfo(Path.Combine(currentDirectory, "appsettings.json")).Exists)
- configurationBuilder.AddJsonFile("appsettings.json", true, true);
- else
- throw new FileNotFoundException($"Unable to find appsetting.json in {currentDirectory}");
+ if (!string.IsNullOrWhiteSpace(aspNetCoreEnvironment) && new FileInfo(Path.Combine(currentDirectory, $"appsettings.{aspNetCoreEnvironment}.json")).Exists)
+ configurationBuilder.AddJsonFile($"appsettings.{aspNetCoreEnvironment}.json", true);
+ else if (new FileInfo(Path.Combine(currentDirectory, "appsettings.json")).Exists)
+ configurationBuilder.AddJsonFile("appsettings.json", true, true);
+ else
+ throw new FileNotFoundException($"Unable to find appsetting.json in {currentDirectory}");
- //var builtConfig = configurationBuilder.Build();
- //var vaultOptions = builtConfig.GetSection("Vault");
+ //var builtConfig = configurationBuilder.Build();
+ //var vaultOptions = builtConfig.GetSection("Vault");
- //configurationBuilder.AddVault(options => {
- // options.Address = vaultOptions["Address"];
+ //configurationBuilder.AddVault(options => {
+ // options.Address = vaultOptions["Address"];
- // options.UnsealKeys = vaultOptions.GetSection("UnsealKeys").Get>();
+ // options.UnsealKeys = vaultOptions.GetSection("UnsealKeys").Get>();
- // options.AuthMethod = EnumerationStringId.FromValue(vaultOptions["AuthMethod"]);
- // options.AppRoleAuthMethod = vaultOptions.GetSection("AppRoleAuthMethod").Get();
- // options.TokenAuthMethod = vaultOptions.GetSection("TokenAuthMethod").Get();
+ // options.AuthMethod = EnumerationStringId.FromValue(vaultOptions["AuthMethod"]);
+ // options.AppRoleAuthMethod = vaultOptions.GetSection("AppRoleAuthMethod").Get();
+ // options.TokenAuthMethod = vaultOptions.GetSection("TokenAuthMethod").Get();
- // options.MountPath = vaultOptions["MountPath"];
- // options.SecretType = vaultOptions["SecretType"];
+ // options.MountPath = vaultOptions["MountPath"];
+ // options.SecretType = vaultOptions["SecretType"];
- // options.ConfigurationMappings = vaultOptions.GetSection("ConfigurationMappings").Get>();
- //});
+ // options.ConfigurationMappings = vaultOptions.GetSection("ConfigurationMappings").Get>();
+ //});
- return configurationBuilder.Build();
- }
+ return configurationBuilder.Build();
}
}
diff --git a/src/Tests/SSHSerivceTests/Abstractions/ServiceBase.cs b/src/Tests/SSHSerivceTests/Abstractions/ServiceBase.cs
index 7d52b27..b608520 100644
--- a/src/Tests/SSHSerivceTests/Abstractions/ServiceBase.cs
+++ b/src/Tests/SSHSerivceTests/Abstractions/ServiceBase.cs
@@ -4,27 +4,26 @@ using Serilog;
using Microsoft.Extensions.Configuration;
-using SSHProvider;
+using MaksIT.SSHProvider;
-namespace MaksIT.Tests.SSHProviderTests.Abstractions {
- public abstract class ServicesBase : ConfigurationBase {
+namespace MaksIT.Tests.SSHProviderTests.Abstractions;
+public abstract class ServicesBase : ConfigurationBase {
- public ServicesBase() : base() { }
+ public ServicesBase() : base() { }
- protected override void ConfigureServices(IServiceCollection services) {
- // configure strongly typed settings objects
- var appSettingsSection = Configuration.GetSection("Configuration");
- services.Configure(appSettingsSection);
- var appSettings = appSettingsSection.Get();
+ protected override void ConfigureServices(IServiceCollection services) {
+ // configure strongly typed settings objects
+ var appSettingsSection = Configuration.GetSection("Configuration");
+ services.Configure(appSettingsSection);
+ var appSettings = appSettingsSection.Get();
- #region configurazione logging
- services.AddLogging(configure => {
- configure.AddSerilog(new LoggerConfiguration()
- //.ReadFrom.Configuration(_configuration)
- .CreateLogger());
- });
- #endregion
+ #region configurazione logging
+ services.AddLogging(configure => {
+ configure.AddSerilog(new LoggerConfiguration()
+ //.ReadFrom.Configuration(_configuration)
+ .CreateLogger());
+ });
+ #endregion
- }
}
}
\ No newline at end of file
diff --git a/src/Tests/SSHSerivceTests/SSHProviderTests.csproj b/src/Tests/SSHSerivceTests/SSHProviderTests.csproj
index 49415f7..cb4a20a 100644
--- a/src/Tests/SSHSerivceTests/SSHProviderTests.csproj
+++ b/src/Tests/SSHSerivceTests/SSHProviderTests.csproj
@@ -10,13 +10,13 @@
-
-
-
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/src/Tests/SSHSerivceTests/UnitTest1.cs b/src/Tests/SSHSerivceTests/UnitTest1.cs
index 6d87802..b4cfa22 100644
--- a/src/Tests/SSHSerivceTests/UnitTest1.cs
+++ b/src/Tests/SSHSerivceTests/UnitTest1.cs
@@ -3,54 +3,53 @@ using System.Security.Cryptography;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-using SSHProvider;
+using MaksIT.SSHProvider;
using MaksIT.Tests.SSHProviderTests.Abstractions;
-namespace SSHSerivceTests {
- public class UnitTest1 : ServicesBase {
+namespace MaksIT.SSHSerivceTests;
+public class UnitTest1 : ServicesBase {
- public readonly string _appPath = AppDomain.CurrentDomain.BaseDirectory;
+ public readonly string _appPath = AppDomain.CurrentDomain.BaseDirectory;
- [Fact]
- public void UploadFile() {
+ [Fact]
+ public void UploadFile() {
- var username = "";
- var password = "";
- var filePath = Path.Combine(_appPath, "randomfile.txt");
- CreateRandomFile(filePath, 1);
+ var username = "";
+ var password = "";
+ var filePath = Path.Combine(_appPath, "randomfile.txt");
+ CreateRandomFile(filePath, 1);
- var logger = ServiceProvider.GetService>();
+ var logger = ServiceProvider.GetService>();
- using var sshService = new SSHService(logger, "192.168.0.10", 22, username, password);
- sshService.Connect();
+ using var sshService = new SSHService(logger, "192.168.0.10", 22, username, password);
+ sshService.Connect();
- var bytes = File.ReadAllBytes(filePath);
+ var bytes = File.ReadAllBytes(filePath);
- logger.LogInformation($"Uploading {filePath} ({bytes.Length:N0} bytes)");
+ logger.LogInformation($"Uploading {filePath} ({bytes.Length:N0} bytes)");
- sshService.RunSudoCommand(password, "chown nginx:nginx /var/www/ssl -R");
- sshService.RunSudoCommand(password, "chmod 777 /var/www/ssl -R");
+ sshService.RunSudoCommand(password, "chown nginx:nginx /var/www/ssl -R");
+ sshService.RunSudoCommand(password, "chmod 777 /var/www/ssl -R");
- sshService.Upload("/var/www/ssl", Path.GetFileName(filePath), bytes);
+ sshService.Upload("/var/www/ssl", Path.GetFileName(filePath), bytes);
- sshService.RunSudoCommand(password, "chown nginx:nginx /var/www/ssl -R");
- sshService.RunSudoCommand(password, "chmod 775 /var/www/ssl -R");
- }
+ sshService.RunSudoCommand(password, "chown nginx:nginx /var/www/ssl -R");
+ sshService.RunSudoCommand(password, "chmod 775 /var/www/ssl -R");
+ }
- private void CreateRandomFile(string filePath, int sizeInMb) {
- // Note: block size must be a factor of 1MB to avoid rounding errors
- const int blockSize = 1024 * 8;
- const int blocksPerMb = (1024 * 1024) / blockSize;
+ private void CreateRandomFile(string filePath, int sizeInMb) {
+ // Note: block size must be a factor of 1MB to avoid rounding errors
+ const int blockSize = 1024 * 8;
+ const int blocksPerMb = (1024 * 1024) / blockSize;
- byte[] data = new byte[blockSize];
+ byte[] data = new byte[blockSize];
- using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider()) {
- using (FileStream stream = File.OpenWrite(filePath)) {
- for (int i = 0; i < sizeInMb * blocksPerMb; i++) {
- crypto.GetBytes(data);
- stream.Write(data, 0, data.Length);
- }
+ using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider()) {
+ using (FileStream stream = File.OpenWrite(filePath)) {
+ for (int i = 0; i < sizeInMb * blocksPerMb; i++) {
+ crypto.GetBytes(data);
+ stream.Write(data, 0, data.Length);
}
}
}