51bf819c37
Cmdlets added: Request-InfisicalCertificate, Get-InfisicalCertificate, Get-InfisicalCertificates. Request supports BySubscriber/ByCa parameter sets, BouncyCastle CSR generation (RSA/ECDSA/Ed25519), local-key generation, -Install/-InstallChain (chain certs routed to Root vs CertificateAuthority by self-signed status), idempotency reuse with -AllowRenewal/-RenewalThresholdDays, local chain reconstruction with -LocalChainOnly opt-out, Infisical bundle fallback when local stores are incomplete, and private-key protection modes (Exportable/LocalOnly/NonExportable/Ephemeral) via -PrivateKeyProtection plus -PersistKey/-MachineKey/-PrivateKeyPath. Install-InfisicalCertificate fix: chain certs were previously dumped into CertificateAuthority unconditionally. They are now routed by Subject==Issuer (self-signed -> Root, otherwise -> CertificateAuthority), matching Request-InfisicalCertificate. Routing centralized in InfisicalCertificateRequestHelpers.GetChainCertificateTargetStore and a new InstallChain(IEnumerable<X509Certificate2>,...) overload. Help: authored Module/PSInfisicalAPI/en-US/PSInfisicalAPI.dll-Help.xml covering all 39 cmdlets (synopsis, description, notes, two examples per cmdlet: one-liner + OrderedDictionary splat with preceding Get- resolvers for IDs/slugs). Build pipeline: build.ps1 stages the help XML into bin/<culture>/ next to the DLL during publish (hard-fails if missing or has zero <command:command> entries). Test-ModuleImports now enumerates every exported cmdlet via Get-Command, cross-checks against expected names, and asserts non-empty synopsis (rejecting auto-generated cmdlet-name fallback), non-empty description, and at least one example with a non-empty <dev:code> block. Tests: 230/230 passing (up from 190).
480 lines
28 KiB
C#
480 lines
28 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Management.Automation;
|
|
using System.Reflection;
|
|
using PSInfisicalAPI.Endpoints;
|
|
using PSInfisicalAPI.Pki;
|
|
using Xunit;
|
|
|
|
namespace PSInfisicalAPI.Tests
|
|
{
|
|
public class CsrAndRequestCmdletTests
|
|
{
|
|
private static readonly Assembly ModuleAssembly = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly;
|
|
|
|
[Fact]
|
|
public void CsrBuilder_Rsa2048_Produces_Pem_Csr_And_PrivateKey_With_Subject_And_Sans()
|
|
{
|
|
InfisicalCsrSubject subject = new InfisicalCsrSubject
|
|
{
|
|
CommonName = "test.contoso.local",
|
|
Organization = "Contoso",
|
|
Country = "US"
|
|
};
|
|
|
|
InfisicalCsrOptions options = new InfisicalCsrOptions { KeyAlgorithm = InfisicalKeyAlgorithm.Rsa, RsaKeySize = 2048 };
|
|
InfisicalCsrResult result = InfisicalCsrBuilder.Build(subject, new[] { "test.contoso.local", "alt.contoso.local" }, new[] { "10.0.0.5" }, options);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Contains("BEGIN CERTIFICATE REQUEST", result.CsrPem);
|
|
Assert.Contains("END CERTIFICATE REQUEST", result.CsrPem);
|
|
Assert.Contains("BEGIN RSA PRIVATE KEY", result.PrivateKeyPem);
|
|
|
|
Org.BouncyCastle.Pkcs.Pkcs10CertificationRequest pkcs10 = ReadCsr(result.CsrPem);
|
|
Assert.True(pkcs10.Verify());
|
|
Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters rsa = Assert.IsAssignableFrom<Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters>(pkcs10.GetPublicKey());
|
|
Assert.Equal(2048, rsa.Modulus.BitLength);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(InfisicalEcCurve.P256, "1.2.840.10045.3.1.7")]
|
|
[InlineData(InfisicalEcCurve.P384, "1.3.132.0.34")]
|
|
public void CsrBuilder_Ecdsa_Produces_Verifiable_Csr(InfisicalEcCurve curve, string expectedCurveOid)
|
|
{
|
|
InfisicalCsrSubject subject = new InfisicalCsrSubject { CommonName = "ec.contoso.local" };
|
|
InfisicalCsrOptions options = new InfisicalCsrOptions { KeyAlgorithm = InfisicalKeyAlgorithm.Ecdsa, EcCurve = curve };
|
|
InfisicalCsrResult result = InfisicalCsrBuilder.Build(subject, new[] { "ec.contoso.local" }, null, options);
|
|
|
|
Assert.Contains("BEGIN CERTIFICATE REQUEST", result.CsrPem);
|
|
Assert.True(result.PrivateKeyPem.Contains("BEGIN EC PRIVATE KEY") || result.PrivateKeyPem.Contains("BEGIN PRIVATE KEY"));
|
|
|
|
Org.BouncyCastle.Pkcs.Pkcs10CertificationRequest pkcs10 = ReadCsr(result.CsrPem);
|
|
Assert.True(pkcs10.Verify());
|
|
Org.BouncyCastle.Crypto.Parameters.ECPublicKeyParameters ec = Assert.IsAssignableFrom<Org.BouncyCastle.Crypto.Parameters.ECPublicKeyParameters>(pkcs10.GetPublicKey());
|
|
Assert.Equal(expectedCurveOid, ec.PublicKeyParamSet.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public void CsrBuilder_Ed25519_Produces_Verifiable_Csr()
|
|
{
|
|
InfisicalCsrSubject subject = new InfisicalCsrSubject { CommonName = "ed.contoso.local" };
|
|
InfisicalCsrOptions options = new InfisicalCsrOptions { KeyAlgorithm = InfisicalKeyAlgorithm.Ed25519 };
|
|
InfisicalCsrResult result = InfisicalCsrBuilder.Build(subject, new[] { "ed.contoso.local" }, null, options);
|
|
|
|
Assert.Contains("BEGIN CERTIFICATE REQUEST", result.CsrPem);
|
|
Assert.Contains("BEGIN PRIVATE KEY", result.PrivateKeyPem);
|
|
|
|
Org.BouncyCastle.Pkcs.Pkcs10CertificationRequest pkcs10 = ReadCsr(result.CsrPem);
|
|
Assert.True(pkcs10.Verify());
|
|
Assert.IsAssignableFrom<Org.BouncyCastle.Crypto.Parameters.Ed25519PublicKeyParameters>(pkcs10.GetPublicKey());
|
|
}
|
|
|
|
[Fact]
|
|
public void CsrBuilder_Rsa_Rejects_Invalid_KeySize()
|
|
{
|
|
InfisicalCsrSubject subject = new InfisicalCsrSubject { CommonName = "test.local" };
|
|
InfisicalCsrOptions options = new InfisicalCsrOptions { KeyAlgorithm = InfisicalKeyAlgorithm.Rsa, RsaKeySize = 1024 };
|
|
Assert.Throws<ArgumentException>(() => InfisicalCsrBuilder.Build(subject, null, null, options));
|
|
}
|
|
|
|
[Fact]
|
|
public void CsrBuilder_Throws_When_CommonName_Missing()
|
|
{
|
|
InfisicalCsrSubject subject = new InfisicalCsrSubject { Organization = "Contoso" };
|
|
Assert.Throws<ArgumentException>(() => InfisicalCsrBuilder.Build(subject, null, null, new InfisicalCsrOptions()));
|
|
}
|
|
|
|
private static Org.BouncyCastle.Pkcs.Pkcs10CertificationRequest ReadCsr(string pem)
|
|
{
|
|
using (System.IO.StringReader reader = new System.IO.StringReader(pem))
|
|
{
|
|
Org.BouncyCastle.OpenSsl.PemReader pemReader = new Org.BouncyCastle.OpenSsl.PemReader(reader);
|
|
object obj = pemReader.ReadObject();
|
|
return Assert.IsType<Org.BouncyCastle.Pkcs.Pkcs10CertificationRequest>(obj);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void MergeSubject_Hashtable_Then_Individual_Params_Override()
|
|
{
|
|
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
|
|
MethodInfo merge = helperType.GetMethod("MergeSubject", BindingFlags.Public | BindingFlags.Static);
|
|
Assert.NotNull(merge);
|
|
|
|
Hashtable subject = new Hashtable { { "CN", "fallback.local" }, { "O", "FallbackOrg" }, { "C", "DE" } };
|
|
object result = merge.Invoke(null, new object[] { subject, "explicit.local", null, null, null, "ExplicitOrg", null, null });
|
|
|
|
PropertyInfo commonNameProp = result.GetType().GetProperty("CommonName");
|
|
PropertyInfo organizationProp = result.GetType().GetProperty("Organization");
|
|
PropertyInfo countryProp = result.GetType().GetProperty("Country");
|
|
|
|
Assert.Equal("explicit.local", commonNameProp.GetValue(result));
|
|
Assert.Equal("ExplicitOrg", organizationProp.GetValue(result));
|
|
Assert.Equal("DE", countryProp.GetValue(result));
|
|
}
|
|
|
|
[Fact]
|
|
public void Candidates_For_SignCertificateBySubscriber_Include_Pki_And_CertManager()
|
|
{
|
|
IReadOnlyList<InfisicalEndpointDefinition> candidates = InfisicalEndpointRegistry.GetCandidates(InfisicalEndpointNames.SignCertificateBySubscriber);
|
|
Assert.Contains(candidates, c => c.Template == "/api/v1/pki/pki-subscribers/{subscriberName}/sign-certificate");
|
|
Assert.Contains(candidates, c => c.Template == "/api/v1/cert-manager/pki-subscribers/{subscriberName}/sign-certificate");
|
|
foreach (InfisicalEndpointDefinition candidate in candidates)
|
|
{
|
|
Assert.Equal("POST", candidate.Method);
|
|
Assert.True(candidate.RequiresAuthorization);
|
|
Assert.True(candidate.ContainsSecretMaterialInResponse);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Candidates_For_SignCertificateByCa_Include_Pki_And_CertManager()
|
|
{
|
|
IReadOnlyList<InfisicalEndpointDefinition> candidates = InfisicalEndpointRegistry.GetCandidates(InfisicalEndpointNames.SignCertificateByCa);
|
|
Assert.Contains(candidates, c => c.Template == "/api/v1/pki/ca/{caId}/sign-certificate");
|
|
Assert.Contains(candidates, c => c.Template == "/api/v1/cert-manager/ca/{caId}/sign-certificate");
|
|
}
|
|
|
|
[Fact]
|
|
public void RequestInfisicalCertificate_Cmdlet_Has_Both_Parameter_Sets()
|
|
{
|
|
Type cmdletType = ModuleAssembly.GetType("PSInfisicalAPI.Cmdlets.RequestInfisicalCertificateCmdlet", true);
|
|
Assert.True(typeof(PSInfisicalAPI.Cmdlets.InfisicalCmdletBase).IsAssignableFrom(cmdletType));
|
|
|
|
CustomAttributeData cmdletData = null;
|
|
foreach (CustomAttributeData candidate in cmdletType.GetCustomAttributesData())
|
|
{
|
|
if (candidate.AttributeType == typeof(CmdletAttribute)) { cmdletData = candidate; break; }
|
|
}
|
|
Assert.NotNull(cmdletData);
|
|
Assert.Equal(VerbsLifecycle.Request, cmdletData.ConstructorArguments[0].Value);
|
|
Assert.Equal("InfisicalCertificate", cmdletData.ConstructorArguments[1].Value);
|
|
|
|
string defaultParameterSetName = null;
|
|
foreach (CustomAttributeNamedArgument named in cmdletData.NamedArguments)
|
|
{
|
|
if (named.MemberName == "DefaultParameterSetName") { defaultParameterSetName = (string)named.TypedValue.Value; break; }
|
|
}
|
|
Assert.Equal("BySubscriber", defaultParameterSetName);
|
|
|
|
Assert.NotNull(cmdletType.GetProperty("PkiSubscriberSlug"));
|
|
Assert.NotNull(cmdletType.GetProperty("CertificateAuthorityId"));
|
|
Assert.NotNull(cmdletType.GetProperty("Subject"));
|
|
Assert.NotNull(cmdletType.GetProperty("CommonName"));
|
|
Assert.NotNull(cmdletType.GetProperty("DnsName"));
|
|
Assert.NotNull(cmdletType.GetProperty("IpAddress"));
|
|
Assert.NotNull(cmdletType.GetProperty("Install"));
|
|
Assert.NotNull(cmdletType.GetProperty("StoreName"));
|
|
Assert.NotNull(cmdletType.GetProperty("StoreLocation"));
|
|
Assert.NotNull(cmdletType.GetProperty("AllowRenewal"));
|
|
Assert.NotNull(cmdletType.GetProperty("RenewalThresholdDays"));
|
|
Assert.NotNull(cmdletType.GetProperty("Force"));
|
|
Assert.NotNull(cmdletType.GetProperty("InstallChain"));
|
|
|
|
PropertyInfo keyAlgorithmProp = cmdletType.GetProperty("KeyAlgorithm");
|
|
PropertyInfo curveProp = cmdletType.GetProperty("Curve");
|
|
Assert.NotNull(keyAlgorithmProp);
|
|
Assert.NotNull(curveProp);
|
|
Assert.Equal(typeof(InfisicalKeyAlgorithm), keyAlgorithmProp.PropertyType);
|
|
Assert.Equal(typeof(InfisicalEcCurve), curveProp.PropertyType);
|
|
|
|
PropertyInfo protectionProp = cmdletType.GetProperty("PrivateKeyProtection");
|
|
Assert.NotNull(protectionProp);
|
|
Assert.Equal(typeof(InfisicalPrivateKeyProtection), protectionProp.PropertyType);
|
|
Assert.NotNull(cmdletType.GetProperty("PersistKey"));
|
|
Assert.NotNull(cmdletType.GetProperty("MachineKey"));
|
|
Assert.NotNull(cmdletType.GetProperty("PrivateKeyPath"));
|
|
Assert.NotNull(cmdletType.GetProperty("LocalChainOnly"));
|
|
|
|
CustomAttributeData outputTypeData = null;
|
|
foreach (CustomAttributeData candidate in cmdletType.GetCustomAttributesData())
|
|
{
|
|
if (candidate.AttributeType == typeof(OutputTypeAttribute)) { outputTypeData = candidate; break; }
|
|
}
|
|
Assert.NotNull(outputTypeData);
|
|
IList<CustomAttributeTypedArgument> outputTypeArgs = (IList<CustomAttributeTypedArgument>)outputTypeData.ConstructorArguments[0].Value;
|
|
Assert.Contains(outputTypeArgs, a => (Type)a.Value == typeof(PSInfisicalAPI.Models.InfisicalCertificateResult));
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildResult_Splits_Chain_Into_Leaf_Intermediates_And_Root()
|
|
{
|
|
(string leafPem, _, string leafThumb) = PemCertificateBuilderTests.CreateSelfSignedExposed("BuildResult.Leaf");
|
|
(string intermediatePem, _, string intermediateThumb) = PemCertificateBuilderTests.CreateSelfSignedExposed("BuildResult.Intermediate");
|
|
(string rootPem, _, string rootThumb) = PemCertificateBuilderTests.CreateSelfSignedExposed("BuildResult.Root");
|
|
|
|
PSInfisicalAPI.Models.InfisicalSignedCertificate signed = new PSInfisicalAPI.Models.InfisicalSignedCertificate
|
|
{
|
|
SerialNumber = "ABC123",
|
|
CertificatePem = leafPem,
|
|
CertificateChainPem = intermediatePem + rootPem,
|
|
IssuingCaCertificatePem = rootPem
|
|
};
|
|
|
|
using (System.Security.Cryptography.X509Certificates.X509Certificate2 leaf = new System.Security.Cryptography.X509Certificates.X509Certificate2(System.Text.Encoding.ASCII.GetBytes(leafPem)))
|
|
{
|
|
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
|
|
MethodInfo buildResult = helperType.GetMethod("BuildResult", BindingFlags.Public | BindingFlags.Static);
|
|
Assert.NotNull(buildResult);
|
|
|
|
PSInfisicalAPI.Models.InfisicalCertificateResult result = (PSInfisicalAPI.Models.InfisicalCertificateResult)buildResult.Invoke(null, new object[] { leaf, signed });
|
|
|
|
Assert.Same(leaf, result.Leaf);
|
|
Assert.Equal("ABC123", result.SerialNumber);
|
|
Assert.Empty(result.Intermediates);
|
|
Assert.NotNull(result.Root);
|
|
Assert.Equal(2, result.Chain.Length);
|
|
Assert.Same(leaf, result.Chain[0]);
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(InfisicalPrivateKeyProtection.LocalOnly, false, false, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.DefaultKeySet)]
|
|
[InlineData(InfisicalPrivateKeyProtection.Exportable, false, false, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.Exportable)]
|
|
[InlineData(InfisicalPrivateKeyProtection.NonExportable, false, false, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.DefaultKeySet)]
|
|
[InlineData(InfisicalPrivateKeyProtection.LocalOnly, true, false, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.PersistKeySet)]
|
|
[InlineData(InfisicalPrivateKeyProtection.LocalOnly, false, true, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.MachineKeySet)]
|
|
[InlineData(InfisicalPrivateKeyProtection.Exportable, true, true, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.Exportable | System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.MachineKeySet | System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.PersistKeySet)]
|
|
public void ResolveKeyStorageFlags_Maps_Protection_And_Switches(InfisicalPrivateKeyProtection protection, bool persist, bool machine, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags expected)
|
|
{
|
|
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
|
|
MethodInfo method = helperType.GetMethod("ResolveKeyStorageFlags", BindingFlags.Public | BindingFlags.Static);
|
|
Assert.NotNull(method);
|
|
|
|
System.Security.Cryptography.X509Certificates.X509KeyStorageFlags actual = (System.Security.Cryptography.X509Certificates.X509KeyStorageFlags)method.Invoke(null, new object[] { protection, persist, machine });
|
|
Assert.Equal(expected, actual);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(InfisicalPrivateKeyProtection.LocalOnly, false, false)]
|
|
[InlineData(InfisicalPrivateKeyProtection.Exportable, false, false)]
|
|
[InlineData(InfisicalPrivateKeyProtection.NonExportable, false, true)]
|
|
[InlineData(InfisicalPrivateKeyProtection.Ephemeral, false, true)]
|
|
[InlineData(InfisicalPrivateKeyProtection.LocalOnly, true, true)]
|
|
[InlineData(InfisicalPrivateKeyProtection.Exportable, true, true)]
|
|
public void ShouldScrubPrivateKeyPem_Returns_Expected(InfisicalPrivateKeyProtection protection, bool hasPath, bool expected)
|
|
{
|
|
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
|
|
MethodInfo method = helperType.GetMethod("ShouldScrubPrivateKeyPem", BindingFlags.Public | BindingFlags.Static);
|
|
Assert.NotNull(method);
|
|
|
|
bool actual = (bool)method.Invoke(null, new object[] { protection, hasPath });
|
|
Assert.Equal(expected, actual);
|
|
}
|
|
|
|
[Fact]
|
|
public void WritePrivateKeyPem_Writes_File_And_Creates_Directory()
|
|
{
|
|
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
|
|
MethodInfo method = helperType.GetMethod("WritePrivateKeyPem", BindingFlags.Public | BindingFlags.Static);
|
|
Assert.NotNull(method);
|
|
|
|
string tempRoot = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "PSInfisicalAPI_PemWrite_" + Guid.NewGuid().ToString("N"));
|
|
string nested = System.IO.Path.Combine(tempRoot, "nested", "key.pem");
|
|
const string pem = "-----BEGIN PRIVATE KEY-----\nMIIBVgIBADANBgkqhkiG9w0BAQEFAA==\n-----END PRIVATE KEY-----\n";
|
|
try
|
|
{
|
|
method.Invoke(null, new object[] { pem, nested });
|
|
Assert.True(System.IO.File.Exists(nested));
|
|
Assert.Equal(pem, System.IO.File.ReadAllText(nested));
|
|
}
|
|
finally
|
|
{
|
|
if (System.IO.Directory.Exists(tempRoot)) { System.IO.Directory.Delete(tempRoot, true); }
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildResultFromExistingLocal_Populates_Leaf_And_Pem_For_Selfsigned()
|
|
{
|
|
(string leafPem, _, string leafThumb) = PemCertificateBuilderTests.CreateSelfSignedExposed("ReuseLookup.Leaf");
|
|
|
|
using (System.Security.Cryptography.X509Certificates.X509Certificate2 leaf = new System.Security.Cryptography.X509Certificates.X509Certificate2(System.Text.Encoding.ASCII.GetBytes(leafPem)))
|
|
{
|
|
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
|
|
MethodInfo build = helperType.GetMethod("BuildResultFromExistingLocal", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(System.Security.Cryptography.X509Certificates.X509Certificate2) }, null);
|
|
Assert.NotNull(build);
|
|
|
|
PSInfisicalAPI.Models.InfisicalCertificateResult result = (PSInfisicalAPI.Models.InfisicalCertificateResult)build.Invoke(null, new object[] { leaf });
|
|
|
|
Assert.Same(leaf, result.Leaf);
|
|
Assert.Equal(leaf.SerialNumber, result.SerialNumber);
|
|
Assert.Contains("BEGIN CERTIFICATE", result.CertificatePem);
|
|
Assert.NotNull(result.Chain);
|
|
Assert.NotEmpty(result.Chain);
|
|
Assert.Same(leaf, result.Chain[0]);
|
|
Assert.Empty(result.Intermediates);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildResultFromExistingLocal_Has_Bundle_Fallback_Overload()
|
|
{
|
|
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
|
|
MethodInfo overload = helperType.GetMethod(
|
|
"BuildResultFromExistingLocal",
|
|
BindingFlags.Public | BindingFlags.Static,
|
|
null,
|
|
new Type[] { typeof(System.Security.Cryptography.X509Certificates.X509Certificate2), typeof(PSInfisicalAPI.Models.InfisicalCertificateBundle) },
|
|
null);
|
|
Assert.NotNull(overload);
|
|
Assert.Equal(typeof(PSInfisicalAPI.Models.InfisicalCertificateResult), overload.ReturnType);
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildResultFromExistingLocal_With_Null_Bundle_Matches_LocalOnly_Behavior()
|
|
{
|
|
(string leafPem, _, _) = PemCertificateBuilderTests.CreateSelfSignedExposed("ReuseLookup.Bundle.Null.Leaf");
|
|
|
|
using (System.Security.Cryptography.X509Certificates.X509Certificate2 leaf = new System.Security.Cryptography.X509Certificates.X509Certificate2(System.Text.Encoding.ASCII.GetBytes(leafPem)))
|
|
{
|
|
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
|
|
MethodInfo overload = helperType.GetMethod(
|
|
"BuildResultFromExistingLocal",
|
|
BindingFlags.Public | BindingFlags.Static,
|
|
null,
|
|
new Type[] { typeof(System.Security.Cryptography.X509Certificates.X509Certificate2), typeof(PSInfisicalAPI.Models.InfisicalCertificateBundle) },
|
|
null);
|
|
|
|
PSInfisicalAPI.Models.InfisicalCertificateResult result = (PSInfisicalAPI.Models.InfisicalCertificateResult)overload.Invoke(null, new object[] { leaf, null });
|
|
|
|
Assert.Same(leaf, result.Leaf);
|
|
Assert.Empty(result.Intermediates);
|
|
Assert.Single(result.Chain);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildResultFromExistingLocal_With_Bundle_Merges_Chain_From_Bundle()
|
|
{
|
|
(string leafPem, _, string leafThumb) = PemCertificateBuilderTests.CreateSelfSignedExposed("ReuseLookup.Bundle.Leaf");
|
|
(string caPem, _, string caThumb) = PemCertificateBuilderTests.CreateSelfSignedExposed("ReuseLookup.Bundle.Ca");
|
|
|
|
using (System.Security.Cryptography.X509Certificates.X509Certificate2 leaf = new System.Security.Cryptography.X509Certificates.X509Certificate2(System.Text.Encoding.ASCII.GetBytes(leafPem)))
|
|
{
|
|
PSInfisicalAPI.Models.InfisicalCertificateBundle bundle = new PSInfisicalAPI.Models.InfisicalCertificateBundle
|
|
{
|
|
SerialNumber = leaf.SerialNumber,
|
|
CertificatePem = leafPem,
|
|
CertificateChainPem = caPem
|
|
};
|
|
|
|
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
|
|
MethodInfo overload = helperType.GetMethod(
|
|
"BuildResultFromExistingLocal",
|
|
BindingFlags.Public | BindingFlags.Static,
|
|
null,
|
|
new Type[] { typeof(System.Security.Cryptography.X509Certificates.X509Certificate2), typeof(PSInfisicalAPI.Models.InfisicalCertificateBundle) },
|
|
null);
|
|
|
|
PSInfisicalAPI.Models.InfisicalCertificateResult result = (PSInfisicalAPI.Models.InfisicalCertificateResult)overload.Invoke(null, new object[] { leaf, bundle });
|
|
|
|
Assert.Same(leaf, result.Leaf);
|
|
Assert.NotNull(result.Root);
|
|
Assert.Equal(caThumb, result.Root.Thumbprint);
|
|
Assert.Equal(2, result.Chain.Length);
|
|
Assert.Same(leaf, result.Chain[0]);
|
|
Assert.Equal(caThumb, result.Chain[1].Thumbprint);
|
|
|
|
Assert.NotNull(result.CertificateChainPem);
|
|
Assert.Contains("BEGIN CERTIFICATE", result.CertificateChainPem);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void GetChainCertificateTargetStore_SelfSigned_Returns_Root()
|
|
{
|
|
using (System.Security.Cryptography.RSA rsa = System.Security.Cryptography.RSA.Create(2048))
|
|
{
|
|
System.Security.Cryptography.X509Certificates.CertificateRequest request = new System.Security.Cryptography.X509Certificates.CertificateRequest(
|
|
"CN=ChainRouting.SelfSigned",
|
|
rsa,
|
|
System.Security.Cryptography.HashAlgorithmName.SHA256,
|
|
System.Security.Cryptography.RSASignaturePadding.Pkcs1);
|
|
using (System.Security.Cryptography.X509Certificates.X509Certificate2 selfSigned = request.CreateSelfSigned(DateTimeOffset.UtcNow.AddMinutes(-5), DateTimeOffset.UtcNow.AddDays(1)))
|
|
{
|
|
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
|
|
MethodInfo classify = helperType.GetMethod("GetChainCertificateTargetStore", BindingFlags.Public | BindingFlags.Static);
|
|
Assert.NotNull(classify);
|
|
|
|
object result = classify.Invoke(null, new object[] { selfSigned });
|
|
Assert.Equal(System.Security.Cryptography.X509Certificates.StoreName.Root, result);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void GetChainCertificateTargetStore_NonSelfSigned_Returns_CertificateAuthority()
|
|
{
|
|
using (System.Security.Cryptography.RSA rootRsa = System.Security.Cryptography.RSA.Create(2048))
|
|
using (System.Security.Cryptography.RSA intermediateRsa = System.Security.Cryptography.RSA.Create(2048))
|
|
{
|
|
System.Security.Cryptography.X509Certificates.CertificateRequest rootRequest = new System.Security.Cryptography.X509Certificates.CertificateRequest(
|
|
"CN=ChainRouting.Root",
|
|
rootRsa,
|
|
System.Security.Cryptography.HashAlgorithmName.SHA256,
|
|
System.Security.Cryptography.RSASignaturePadding.Pkcs1);
|
|
rootRequest.CertificateExtensions.Add(new System.Security.Cryptography.X509Certificates.X509BasicConstraintsExtension(true, false, 0, true));
|
|
|
|
using (System.Security.Cryptography.X509Certificates.X509Certificate2 rootCert = rootRequest.CreateSelfSigned(DateTimeOffset.UtcNow.AddMinutes(-5), DateTimeOffset.UtcNow.AddDays(1)))
|
|
{
|
|
System.Security.Cryptography.X509Certificates.CertificateRequest intermediateRequest = new System.Security.Cryptography.X509Certificates.CertificateRequest(
|
|
"CN=ChainRouting.Intermediate",
|
|
intermediateRsa,
|
|
System.Security.Cryptography.HashAlgorithmName.SHA256,
|
|
System.Security.Cryptography.RSASignaturePadding.Pkcs1);
|
|
intermediateRequest.CertificateExtensions.Add(new System.Security.Cryptography.X509Certificates.X509BasicConstraintsExtension(true, false, 0, true));
|
|
|
|
byte[] serial = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
|
|
using (System.Security.Cryptography.X509Certificates.X509Certificate2 intermediate = intermediateRequest.Create(rootCert, DateTimeOffset.UtcNow.AddMinutes(-5), DateTimeOffset.UtcNow.AddDays(1), serial))
|
|
{
|
|
Assert.NotEqual(intermediate.Subject, intermediate.Issuer);
|
|
|
|
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
|
|
MethodInfo classify = helperType.GetMethod("GetChainCertificateTargetStore", BindingFlags.Public | BindingFlags.Static);
|
|
|
|
object result = classify.Invoke(null, new object[] { intermediate });
|
|
Assert.Equal(System.Security.Cryptography.X509Certificates.StoreName.CertificateAuthority, result);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void InstallChain_Has_X509Collection_Overload()
|
|
{
|
|
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
|
|
Type loggerType = ModuleAssembly.GetType("PSInfisicalAPI.Logging.IInfisicalLogger", true);
|
|
|
|
MethodInfo overload = helperType.GetMethod(
|
|
"InstallChain",
|
|
BindingFlags.Public | BindingFlags.Static,
|
|
null,
|
|
new Type[]
|
|
{
|
|
typeof(System.Collections.Generic.IEnumerable<System.Security.Cryptography.X509Certificates.X509Certificate2>),
|
|
typeof(System.Security.Cryptography.X509Certificates.StoreLocation),
|
|
typeof(bool),
|
|
loggerType,
|
|
typeof(string)
|
|
},
|
|
null);
|
|
|
|
Assert.NotNull(overload);
|
|
Assert.Equal(typeof(void), overload.ReturnType);
|
|
}
|
|
|
|
[Fact]
|
|
public void InstallInfisicalCertificateCmdlet_Uses_ChainRouting_Helper()
|
|
{
|
|
Type cmdletType = ModuleAssembly.GetType("PSInfisicalAPI.Cmdlets.InstallInfisicalCertificateCmdlet", true);
|
|
Assert.NotNull(cmdletType);
|
|
|
|
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
|
|
MethodInfo classify = helperType.GetMethod("GetChainCertificateTargetStore", BindingFlags.Public | BindingFlags.Static);
|
|
Assert.NotNull(classify);
|
|
}
|
|
}
|
|
}
|