diff --git a/documentation/Connect-PnPOnline.md b/documentation/Connect-PnPOnline.md index 27cddc667..a44eced5b 100644 --- a/documentation/Connect-PnPOnline.md +++ b/documentation/Connect-PnPOnline.md @@ -282,6 +282,18 @@ Connects to the Azure AD with WAM (aka native Windows authentication prompt), ac WAM is a more secure & faster way of authenticating in Windows OS. It supports Windows Hello, FIDO keys , conditional access policies and more. +### EXAMPLE 9 +```powershell +$keyStorageflags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet + +Connect-PnPOnline -Url "contoso.sharepoint.com" -ClientId 6c5c98c7-e05a-4a0f-bcfa-0cfc65aa1f28 -CertificateBase64Encoded $base64encodedstring -X509KeyStorageFlags $keyStorageflags -Tenant 'contoso.onmicrosoft.com' +``` + +Connects using an Azure Active Directory registered application using a certificate with a private key that has been base64 encoded. +See [Security App-only EntraId guidance](https://learn.microsoft.com/sharepoint/dev/solution-guidance/security-apponly-azuread) for a sample on how to get started. + +See [X509 key storage flags](https://learn.microsoft.com/dotnet/api/system.security.cryptography.x509certificates.x509keystorageflags) for information on how to configure key storage when creating the certificate. + ## PARAMETERS ### -AccessToken @@ -874,6 +886,24 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -X509KeyStorageFlags + +Defines where and how to import the private key of an X.509 certificate. + +This enumeration supports a bitwise combination of its member values. + +```yaml +Type: System.Security.Cryptography.X509Certificates.X509KeyStorageFlags +Parameter Sets: App-Only with Azure Active Directory +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ## RELATED LINKS [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) diff --git a/src/Commands/Base/ConnectOnline.cs b/src/Commands/Base/ConnectOnline.cs index 6b12588f2..6c532b97c 100644 --- a/src/Commands/Base/ConnectOnline.cs +++ b/src/Commands/Base/ConnectOnline.cs @@ -189,6 +189,9 @@ public class ConnectOnline : BasePSCmdlet [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADCERTIFICATE)] public string CertificateBase64Encoded; + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADCERTIFICATE)] + public X509KeyStorageFlags X509KeyStorageFlags; + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADCERTIFICATE)] public SecureString CertificatePassword; @@ -631,10 +634,11 @@ private PnPConnection ConnectAppOnlyWithCertificate() throw new FileNotFoundException("Certificate not found"); } - X509Certificate2 certificate = CertificateHelper.GetCertificateFromPath(this, CertificatePath, CertificatePassword); + X509Certificate2 certificate = CertificateHelper.GetCertificateFromPath(this, CertificatePath, CertificatePassword, X509KeyStorageFlags); if (Connection?.ClientId == ClientId && Connection?.Tenant == Tenant && Connection?.Certificate?.Thumbprint == certificate.Thumbprint) + { ReuseAuthenticationManager(); } @@ -644,7 +648,13 @@ private PnPConnection ConnectAppOnlyWithCertificate() else if (ParameterSpecified(nameof(CertificateBase64Encoded))) { var certificateBytes = Convert.FromBase64String(CertificateBase64Encoded); - var certificate = new X509Certificate2(certificateBytes, CertificatePassword); + if (!ParameterSpecified(nameof(X509KeyStorageFlags))) + { + X509KeyStorageFlags = X509KeyStorageFlags.Exportable | + X509KeyStorageFlags.MachineKeySet | + X509KeyStorageFlags.PersistKeySet; + } + var certificate = new X509Certificate2(certificateBytes, CertificatePassword, X509KeyStorageFlags); if (Connection?.ClientId == ClientId && Connection?.Tenant == Tenant && diff --git a/src/Commands/Utilities/CertificateHelper.cs b/src/Commands/Utilities/CertificateHelper.cs index cbb4a9a95..66c8956cb 100644 --- a/src/Commands/Utilities/CertificateHelper.cs +++ b/src/Commands/Utilities/CertificateHelper.cs @@ -128,10 +128,15 @@ internal static X509Certificate2 GetCertificateFromStore(string thumbprint) /// Cmdlet executing this function /// Path to the private key certificate file /// Password to open the certificate or NULL if no password set on the certificate + /// Key storage flags for created X509Certificate2 /// X509Certificate2 instance /// Thrown if the certificate cannot be read /// Thrown if the certificate cannot be found at the provided path - internal static X509Certificate2 GetCertificateFromPath(Cmdlet cmdlet, string certificatePath, SecureString certificatePassword) + internal static X509Certificate2 GetCertificateFromPath(Cmdlet cmdlet, string certificatePath, SecureString certificatePassword, + X509KeyStorageFlags x509KeyStorageFlags = + X509KeyStorageFlags.Exportable | + X509KeyStorageFlags.MachineKeySet | + X509KeyStorageFlags.PersistKeySet) { if (System.IO.File.Exists(certificatePath)) { @@ -152,9 +157,8 @@ internal static X509Certificate2 GetCertificateFromPath(Cmdlet cmdlet, string ce var certificate = new X509Certificate2( certificateBytes, certificatePassword, - X509KeyStorageFlags.Exportable | - X509KeyStorageFlags.MachineKeySet | - X509KeyStorageFlags.PersistKeySet); + x509KeyStorageFlags + ); return certificate; } catch (CryptographicException e)