Skip to content

Refactor certificate handling to use UserKeySet for X509KeyStorageFlags and remove local admin requirement for cmdlets #4853

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions src/Commands/AzureAD/RegisterAzureADApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,6 @@ public class RegisterAzureADApp : BasePSCmdlet, IDynamicParameters

protected override void ProcessRecord()
{
if (!PSUtility.IsUserLocalAdmin())
{
throw new PSArgumentException("Running this cmdlet requires elevated permissions (Run as Admin) to generate a certificate.");
}

if (ParameterSpecified(nameof(Store)) && !OperatingSystem.IsWindows())
{
throw new PSArgumentException("The Store parameter is only supported on Microsoft Windows");
Expand Down Expand Up @@ -470,7 +465,7 @@ private X509Certificate2 GetCertificate(PSObject record)

try
{
cert = new X509Certificate2(CertificatePath, CertificatePassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
cert = new X509Certificate2(CertificatePath, CertificatePassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.PersistKeySet);
}
catch (CryptographicException e) when (e.Message.Contains("The specified password is not correct"))
{
Expand Down Expand Up @@ -658,7 +653,6 @@ private void StartConsentFlow(string loginEndPoint, AzureADApp azureApp, string
progressRecord.RecordType = ProgressRecordType.Completed;
WriteProgress(progressRecord);


if (!Stopping)
{
if (ParameterSpecified(nameof(DeviceLogin)))
Expand All @@ -685,9 +679,7 @@ private void StartConsentFlow(string loginEndPoint, AzureADApp azureApp, string
authManager.ClearTokenCache();
authManager.GetAccessToken(resource, Microsoft.Identity.Client.Prompt.Consent);
}

}
WriteObject(record);
}

WriteObject(record);
Expand Down
5 changes: 0 additions & 5 deletions src/Commands/AzureAD/RegisterEntraIDAppForInteractiveLogin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,6 @@ public class RegisterEntraIDAppForInteractiveLogin : BasePSCmdlet, IDynamicParam

protected override void ProcessRecord()
{
if (!PSUtility.IsUserLocalAdmin())
{
throw new PSArgumentException("Running this cmdlet requires elevated permissions (Run as Admin) to generate a certificate.");
}

var redirectUri = "http://localhost";
// if (ParameterSpecified(nameof(DeviceLogin)) || OperatingSystem.IsMacOS())
if (ParameterSpecified(nameof(DeviceLogin)) || OperatingSystem.IsMacOS())
Expand Down
14 changes: 6 additions & 8 deletions src/Commands/Base/ConnectOnline.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using PnP.PowerShell.Commands.Base.PipeBinds;
using Microsoft.SharePoint.Client;
using PnP.Framework;
using PnP.PowerShell.Commands.Base.PipeBinds;
using PnP.PowerShell.Commands.Enums;
using PnP.PowerShell.Commands.Model;
using PnP.PowerShell.Commands.Provider;
Expand All @@ -13,9 +15,7 @@
using System.Threading;
using System.Threading.Tasks;
using File = System.IO.File;
using PnP.Framework;
using Resources = PnP.PowerShell.Commands.Properties.Resources;
using Microsoft.SharePoint.Client;

namespace PnP.PowerShell.Commands.Base
{
Expand Down Expand Up @@ -591,7 +591,7 @@ private PnPConnection ConnectAppOnlyWithCertificate()
if (!ParameterSpecified(nameof(X509KeyStorageFlags)))
{
X509KeyStorageFlags = X509KeyStorageFlags.Exportable |
X509KeyStorageFlags.MachineKeySet |
X509KeyStorageFlags.UserKeySet |
X509KeyStorageFlags.PersistKeySet;
}

Expand All @@ -612,7 +612,7 @@ private PnPConnection ConnectAppOnlyWithCertificate()
if (!ParameterSpecified(nameof(X509KeyStorageFlags)))
{
X509KeyStorageFlags = X509KeyStorageFlags.Exportable |
X509KeyStorageFlags.MachineKeySet |
X509KeyStorageFlags.UserKeySet |
X509KeyStorageFlags.PersistKeySet;
}
var certificate = new X509Certificate2(certificateBytes, CertificatePassword, X509KeyStorageFlags);
Expand Down Expand Up @@ -821,7 +821,7 @@ private PnPConnection ConnectEnvironmentVariable(InitializationType initializati
if (!ParameterSpecified(nameof(X509KeyStorageFlags)))
{
X509KeyStorageFlags = X509KeyStorageFlags.Exportable |
X509KeyStorageFlags.MachineKeySet |
X509KeyStorageFlags.UserKeySet |
X509KeyStorageFlags.PersistKeySet;
}

Expand Down Expand Up @@ -984,8 +984,6 @@ private PSCredential GetCredentials()
private string GetAppId()
{
var connectionUri = new Uri(Url);


// Try to get the credentials by full url
string appId = PnPConnection.GetCacheClientId(connectionUri.ToString()) ?? Utilities.CredentialManager.GetAppId(connectionUri.ToString());
if (appId == null)
Expand Down
12 changes: 6 additions & 6 deletions src/Commands/Base/GetAzureCertificate.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System.Management.Automation;
using PnP.PowerShell.Commands.Utilities;
using System;
using System.Linq;
using System.Management.Automation;
using System.Security;
using System.Security.Cryptography.X509Certificates;
using PnP.PowerShell.Commands.Utilities;
using System.Security.Cryptography;
using System.Linq;
using System.Security.Cryptography.X509Certificates;

namespace PnP.PowerShell.Commands.Base
{
Expand All @@ -28,7 +28,7 @@ protected override void ProcessRecord()
}
if (System.IO.File.Exists(Path))
{
var certificate = new X509Certificate2(Path, Password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);
var certificate = new X509Certificate2(Path, Password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.UserKeySet);
WriteAzureCertificateOutput(this, certificate, Password);
}
else
Expand Down Expand Up @@ -88,7 +88,7 @@ internal static void WriteAzureCertificateOutput(BasePSCmdlet cmdlet, X509Certif
certificate: CertificateHelper.CertificateToBase64(certificate),
privateKey: CertificateHelper.PrivateKeyToBase64(certificate),
sanNames: certificate.Extensions.Cast<X509Extension>()
.Where(n => n.Oid.FriendlyName=="Subject Alternative Name")
.Where(n => n.Oid.FriendlyName == "Subject Alternative Name")
.Select(n => new AsnEncodedData(n.Oid, n.RawData))
.Select(n => n.Format(false))
.FirstOrDefault().Split(',', StringSplitOptions.TrimEntries)
Expand Down
5 changes: 0 additions & 5 deletions src/Commands/Base/NewAzureCertificate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,6 @@ protected override void ProcessRecord()
throw new PSArgumentException("The Store parameter is only supported on Microsoft Windows");
}

if (!PSUtility.IsUserLocalAdmin())
{
throw new PSArgumentException("Running this cmdlet requires elevated permissions (Run as Admin) to generate a certificate.");
}

if (ValidYears < 1 || ValidYears > 30)
{
ValidYears = 10;
Expand Down
1 change: 1 addition & 0 deletions src/Commands/PnP.PowerShell.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
<PackageReference Include="PnP.Core.Admin" Version="1.14.*-*" Condition="'$(IsRelease)' == '1'" />

<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.70.2" />
<PackageReference Include="Microsoft.Bcl.Cryptography" Version="9.0.2" />

<ProjectReference Include="..\ALC\PnP.PowerShell.ALC.csproj" />

Expand Down
12 changes: 9 additions & 3 deletions src/Commands/Utilities/CertificateHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ internal static X509Certificate2 GetCertificateFromStore(string thumbprint)
internal static X509Certificate2 GetCertificateFromPath(Cmdlet cmdlet, string certificatePath, SecureString certificatePassword,
X509KeyStorageFlags x509KeyStorageFlags =
X509KeyStorageFlags.Exportable |
X509KeyStorageFlags.MachineKeySet |
X509KeyStorageFlags.UserKeySet |
X509KeyStorageFlags.PersistKeySet)
{
if (System.IO.File.Exists(certificatePath))
Expand Down Expand Up @@ -268,7 +268,7 @@ internal static X509Certificate2 CreateSelfSignedCertificate(string commonName,
X500DistinguishedName distinguishedName = new X500DistinguishedName(distinguishedNameString);

#pragma warning disable CA1416 // Validate platform compatibility
using (RSA rsa = Platform.IsWindows ? MakeExportable(new RSACng(2048)) : RSA.Create(2048))
using (RSA rsa = RSA.Create(2048))
{
var request = new CertificateRequest(distinguishedName, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

Expand All @@ -287,8 +287,14 @@ internal static X509Certificate2 CreateSelfSignedCertificate(string commonName,
{
certificate.FriendlyName = friendlyName;
}
string passString = password != null ? CredentialManager.SecureStringToString(password) : null;

return new X509Certificate2(certificate.Export(X509ContentType.Pfx, password), password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
if (PSUtility.PSVersion == "7.5")
{
return X509CertificateLoader.LoadPkcs12(certificate.Export(X509ContentType.Pfx, password), passString, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.PersistKeySet);
}

return new X509Certificate2(certificate.Export(X509ContentType.Pfx, password), password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.PersistKeySet);
}
#pragma warning restore CA1416 // Validate platform compatibility
}
Expand Down
14 changes: 7 additions & 7 deletions src/Commands/Utilities/CredentialManager.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
using Microsoft.Identity.Client.Extensions.Msal;
using Microsoft.Win32.SafeHandles;
using PnP.Framework.Modernization.Cache;
using System;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
Expand All @@ -7,9 +10,6 @@
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using Microsoft.Identity.Client.Extensions.Msal;
using Microsoft.Win32.SafeHandles;
using PnP.Framework.Modernization.Cache;
using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;

[assembly: InternalsVisibleTo("PnP.PowerShell.Tests")]
Expand Down Expand Up @@ -498,7 +498,7 @@ private static PSCredential ReadMacOSKeyChainEntry(string applicationName)
}
return null;
}
private static void WriteMacOSKeyChainEntry(string applicationName,string password)
private static void WriteMacOSKeyChainEntry(string applicationName, string password)
{
var keychain = new MacOSKeychain();
keychain.AddOrUpdate(applicationName, applicationName, password.ToByteArray());
Expand All @@ -507,14 +507,14 @@ private static void WriteMacOSKeyChainEntry(string applicationName,string passwo
private static bool DeleteMacOSKeyChainEntry(string name)
{
var keychain = new MacOSKeychain();
return keychain.Remove(name,name);
return keychain.Remove(name, name);
// var cmd = $"/usr/bin/security delete-generic-password -s '{name}'";
// var output = Shell.Bash(cmd);
// var success = output.Count > 1 && !output[0].StartsWith("security:");
// return success;
}

private static string SecureStringToString(SecureString value)
public static string SecureStringToString(SecureString value)
{
IntPtr valuePtr = IntPtr.Zero;
try
Expand Down
Loading