diff --git a/samples/manage/azure-sql-db-managed-instance/upload-tde-certificate/README.md b/samples/manage/azure-sql-db-managed-instance/upload-tde-certificate/README.md new file mode 100644 index 00000000..6dbf2a63 --- /dev/null +++ b/samples/manage/azure-sql-db-managed-instance/upload-tde-certificate/README.md @@ -0,0 +1,84 @@ +# Upload certificate to Azure SQL Managed Instance using Azure PowerShell + +Script that takes path to .cer and .pvk files and uploads them to SQL MI. See [more](https://docs.microsoft.com/en-us/azure/sql-database/sql-database-managed-instance-migrate-tde-certificate). + +### Contents + +[About this sample](#about-this-sample)
+[Before you begin](#before-you-begin)
+[Run this sample](#run-this-sample)
+[Sample details](#sample-details)
+[Disclaimers](#disclaimers)
+[Related links](#related-links)
+ + + + +## About this sample + +- **Applies to:** Azure SQL Database +- **Key features:** Managed Instance +- **Workload:** n/a +- **Programming Language:** PowerShell +- **Authors:** Srdan Bozovic +- **Update history:** n/a + + + +## Before you begin + +To run this sample, you need the following prerequisites. + +**Software prerequisites:** + +1. Running Microsoft Windows operating system (as script uses PInvoke for crypto api) +2. PowerShell 5.0 or higher installed +3. AzureRM.Sql module version 4.10.0 or higher + +**Azure prerequisites:** + +1. Permission to manage Azure SQL Managed Instance + + + +## Run this sample + +Run the script below from either Windows or Azure Cloud Shell + +```powershell + +$scriptUrlBase = 'https://raw.githubusercontent.com/Microsoft/sql-server-samples/master/samples/manage/azure-sql-db-managed-instance/upload-tde-certificate' + +$parameters = @{ + subscriptionId = '' + resourceGroupName = '' + managedInstanceName = '' + publicKeyFile = '' + privateKeyFile = '' + password = '' + } + +Invoke-Command -ScriptBlock ([Scriptblock]::Create((iwr ($scriptUrlBase+'/uploadTDECertificate.ps1?t='+ [DateTime]::Now.Ticks)).Content)) -ArgumentList $parameters + +``` + + + +## Sample details + +This sample shows how to convert .cer and .pvk files to base64 .pfx blob and upload it to SQL MI using PowerShell. + + + +## Disclaimers +The scripts and this guide are copyright Microsoft Corporations and are provided as samples. They are not part of any Azure service and are not covered by any SLA or other Azure-related agreements. They are provided as-is with no warranties express or implied. Microsoft takes no responsibility for the use of the scripts or the accuracy of this document. Familiarize yourself with the scripts before using them. + + + +## Related Links + + +For more information, see these articles: + +- [What is a Managed Instance (preview)?](https://docs.microsoft.com/azure/sql-database/sql-database-managed-instance) +- [Configure a VNet for Azure SQL Database Managed Instance](https://docs.microsoft.com/azure/sql-database/sql-database-managed-instance-vnet-configuration) \ No newline at end of file diff --git a/samples/manage/azure-sql-db-managed-instance/upload-tde-certificate/uploadTDECertificate.ps1 b/samples/manage/azure-sql-db-managed-instance/upload-tde-certificate/uploadTDECertificate.ps1 new file mode 100644 index 00000000..080d574e --- /dev/null +++ b/samples/manage/azure-sql-db-managed-instance/upload-tde-certificate/uploadTDECertificate.ps1 @@ -0,0 +1,471 @@ +$parameters = $args[0] + +$subscriptionId = $parameters['subscriptionId'] +$resourceGroupName = $parameters['resourceGroupName'] +$managedInstanceName = $parameters['managedInstanceName'] +$publicKeyFile = $parameters['publicKeyFile'] +$privateKeyFile = $parameters['privateKeyFile'] +$password = $parameters['password'] + +$Assem = @() + +$Source = @" +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace CL +{ + public static class CertUtil + { + private static class Native + { + public const uint ALG_CLASS_DATA_ENCRYPT = (3 << 13); + public const uint ALG_CLASS_HASH = (4 << 13); + public const uint ALG_SID_RC4 = 1; + public const uint ALG_SID_SHA1 = 4; + public const uint ALG_TYPE_ANY = 0; + public const uint ALG_TYPE_STREAM = (4 << 9); + public const uint AT_KEYEXCHANGE = 1; + public const uint BLGP_STRONG_KEY_LENGTH = 0x0080; + public const uint CALG_RC4 = (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_STREAM | ALG_SID_RC4); + public const uint CALG_SHA1 = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA1); + public const uint CERT_KEY_PROV_INFO_PROP_ID = 2; + public const uint CERT_SET_KEY_PROV_HANDLE_PROP_ID = 0x00000001; + public const uint CERT_STORE_ADD_REPLACE_EXISTING_INHERIT_PROPERTIES = 5; + public const uint CERT_STORE_PROV_MEMORY = 2; + public const uint CERT_CLOSE_STORE_CHECK_FLAG = 2; + public const uint CRYPT_NEWKEYSET = 0x00000008; + public const uint EXPORT_PRIVATE_KEYS = 0x0004; + public const uint PKCS_7_ASN_ENCODING = 0x00010000; + public const uint PROV_RSA_FULL = 1; + public const uint X509_ASN_ENCODING = 0x00000001; + + + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool CryptAcquireContext(ref IntPtr phProv, string szContainer, string szProvider, uint dwProvType, uint dwFlags); + + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool CryptReleaseContext(IntPtr hProv, uint dwFlags); + + + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool CryptCreateHash(IntPtr hProv, uint Algid, IntPtr hKey, uint dwFlags, ref IntPtr phHash); + + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool CryptHashData(IntPtr hHash, byte[] pbData, uint dwDataLen, uint dwFlags); + + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool CryptDestroyHash(IntPtr hHash); + + + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool CryptDeriveKey(IntPtr hProv, uint Algid, IntPtr hBaseData, uint dwFlags, ref IntPtr phKey); + + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool CryptImportKey(IntPtr hProv, byte[] pbData, UInt32 dwDataLen, IntPtr hPubKey, UInt32 dwFlags, ref IntPtr phKey); + + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool CryptDestroyKey(IntPtr phKey); + + + [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool CryptExportPublicKeyInfo(IntPtr hCryptProv, uint dwKeySpec, uint dwCertEncodingType, IntPtr pInfo, ref int pcbInfo); + + [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr CertCreateCertificateContext(uint dwCertEncodingType, IntPtr pbCertEncoded, int cbCertEncoded); + + [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool CertFreeCertificateContext(IntPtr pCertContext); + + [DllImport("crypt32.DLL", CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr CertOpenStore(uint dwStoreProvider, uint dwEncodingType, IntPtr hCryptProv, uint dwFlags, IntPtr pvPara); + + [DllImport("crypt32.DLL", CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr CertCloseStore(IntPtr hCertStore, uint dwFlags); + + [DllImport("crypt32.DLL", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool CertAddCertificateContextToStore(IntPtr hCertStore, IntPtr pCertContext, uint dwAddDisposition, ref IntPtr ppStoreContext); + + [DllImport("crypt32.DLL", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool CertSetCertificateContextProperty(IntPtr pCertContext, uint dwPropId, uint dwFlags, ref CRYPT_KEY_PROV_INFO pvData); + + [DllImport("crypt32.DLL", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool PFXExportCertStoreEx(IntPtr hStore, ref CRYPT_DATA_BLOB pPFX, IntPtr szPassword, IntPtr pvPara, uint dwFlags); + + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct CRYPT_KEY_PROV_INFO + { + [MarshalAs(UnmanagedType.LPWStr)] + public string pwszContainerName; + [MarshalAs(UnmanagedType.LPWStr)] + public string pwszProvName; + public uint dwProvType; + public uint dwFlags; + public uint cProvParam; + public IntPtr rgProvParam; + public uint dwKeySpec; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct CRYPT_DATA_BLOB + { + public int cbData; + public IntPtr pbData; + } + } + + + private static bool ImportPvk(IntPtr phProv, byte[] privateKeyBytes, string password, ref IntPtr privateKey) + { + privateKey = IntPtr.Zero; + var result = false; + using (var ms = new MemoryStream(privateKeyBytes)) + using (var r = new BinaryReader(ms)) + { + var magic = r.ReadInt32(); + var reserved = r.ReadInt32(); + var keytype = r.ReadInt32(); + var encrypted = r.ReadInt32(); + var saltLength = r.ReadInt32(); + var keyLength = r.ReadInt32(); + var salt = r.ReadBytes(saltLength); + + var key = r.ReadBytes(keyLength); + + var enc = Encoding.UTF8; + var passwordBytes = enc.GetBytes(password); + + var pHash = IntPtr.Zero; + var phKey = IntPtr.Zero; + + if (!Native.CryptCreateHash(phProv, Native.CALG_SHA1, IntPtr.Zero, 0, ref pHash)) + { + goto End; + } + + if (!Native.CryptHashData(pHash, salt, (uint)salt.Length, 0)) + { + goto End; + } + + if (!Native.CryptHashData(pHash, passwordBytes, (uint)passwordBytes.Length, 0)) + { + goto End; + } + + if (!Native.CryptDeriveKey(phProv, Native.CALG_RC4, pHash, Native.BLGP_STRONG_KEY_LENGTH << 16, ref phKey)) + { + goto End; + } + + if (!Native.CryptImportKey(phProv, key, (uint)keyLength, phKey, 1, ref privateKey)) + { + goto End; + } + + result = true; + + End: + { + if (pHash != IntPtr.Zero) + { + Native.CryptDestroyHash(pHash); + } + if (phKey != IntPtr.Zero) + { + Native.CryptDestroyKey(phKey); + } + } + return result; + } + } + + public static string CerPvkToPfx(string publicKeyBlob, string privateKeyBlob, string password) + { + string result = null; + + var publicKeyBytes = Convert.FromBase64String(publicKeyBlob); + var privateKeyBytes = Convert.FromBase64String(privateKeyBlob); + + var container = Guid.NewGuid().ToString(); + + var phProv = IntPtr.Zero; + var pPublicKeyInfo = IntPtr.Zero; + var pPrivateKey = IntPtr.Zero; + var pPublicKey = IntPtr.Zero; + var pCertificateContext = IntPtr.Zero; + var pMemoryStore = IntPtr.Zero; + var pNewCertificateContext = IntPtr.Zero; + var pszPassword = IntPtr.Zero; + GCHandle pfxDataHandle = default(GCHandle); + + if (!Native.CryptAcquireContext(ref phProv, container, null, 1, Native.CRYPT_NEWKEYSET)) + { + goto End; + } + + if (!ImportPvk(phProv, privateKeyBytes, password, ref pPrivateKey)) + { + goto End; + } + + var keySpec = Native.AT_KEYEXCHANGE; + var pubKeyInfoCb = 0; + if (!Native.CryptExportPublicKeyInfo(phProv, keySpec, Native.PKCS_7_ASN_ENCODING | Native.X509_ASN_ENCODING, IntPtr.Zero, ref pubKeyInfoCb)) + { + goto End; + } + + pPublicKeyInfo = Marshal.AllocHGlobal(pubKeyInfoCb); + if (!Native.CryptExportPublicKeyInfo(phProv, keySpec, Native.PKCS_7_ASN_ENCODING | Native.X509_ASN_ENCODING, pPublicKeyInfo, ref pubKeyInfoCb)) + { + goto End; + } + + var publicKeySize = publicKeyBytes.Length; + pPublicKey = Marshal.AllocHGlobal(publicKeySize); + Marshal.Copy(publicKeyBytes, 0, pPublicKey, publicKeySize); + pCertificateContext = Native.CertCreateCertificateContext(Native.PKCS_7_ASN_ENCODING | Native.X509_ASN_ENCODING, pPublicKey, publicKeySize); + if (pCertificateContext == IntPtr.Zero) + { + goto End; + } + + pMemoryStore = Native.CertOpenStore(Native.CERT_STORE_PROV_MEMORY, 0, IntPtr.Zero, 0, IntPtr.Zero); + if (pMemoryStore == IntPtr.Zero) + { + goto End; + } + + if (!Native.CertAddCertificateContextToStore(pMemoryStore, pCertificateContext, Native.CERT_STORE_ADD_REPLACE_EXISTING_INHERIT_PROPERTIES, ref pNewCertificateContext)) + { + goto End; + } + + var cryptKeyProvInfo = new Native.CRYPT_KEY_PROV_INFO(); + cryptKeyProvInfo.pwszContainerName = container; + cryptKeyProvInfo.dwProvType = Native.PROV_RSA_FULL; + cryptKeyProvInfo.dwFlags = Native.CERT_SET_KEY_PROV_HANDLE_PROP_ID; + cryptKeyProvInfo.dwKeySpec = keySpec; + + if (!Native.CertSetCertificateContextProperty(pNewCertificateContext, Native.CERT_KEY_PROV_INFO_PROP_ID, 0, ref cryptKeyProvInfo)) + { + goto End; + } + + var cryptDataBlob = new Native.CRYPT_DATA_BLOB(); + pszPassword = Marshal.StringToHGlobalUni(password); + if (!Native.PFXExportCertStoreEx(pMemoryStore, ref cryptDataBlob, pszPassword, IntPtr.Zero, Native.EXPORT_PRIVATE_KEYS)) + { + goto End; + } + + var pfxData = new byte[cryptDataBlob.cbData]; + pfxDataHandle = GCHandle.Alloc(pfxData, GCHandleType.Pinned); + cryptDataBlob.pbData = pfxDataHandle.AddrOfPinnedObject(); + + if (!Native.PFXExportCertStoreEx(pMemoryStore, ref cryptDataBlob, pszPassword, IntPtr.Zero, Native.EXPORT_PRIVATE_KEYS)) + { + goto End; + } + + result = Convert.ToBase64String(pfxData); + + End: + { + if (pPublicKey != IntPtr.Zero) + { + Marshal.FreeHGlobal(pPublicKey); + } + if (pPublicKeyInfo != IntPtr.Zero) + { + Marshal.FreeHGlobal(pPublicKeyInfo); + } + if (pPrivateKey != IntPtr.Zero) + { + Native.CryptDestroyKey(pPrivateKey); + } + if (pCertificateContext != IntPtr.Zero) + { + Native.CertFreeCertificateContext(pCertificateContext); + } + if (pNewCertificateContext != IntPtr.Zero) + { + Native.CertFreeCertificateContext(pNewCertificateContext); + } + if (pMemoryStore != IntPtr.Zero) + { + Native.CertCloseStore(pMemoryStore, Native.CERT_CLOSE_STORE_CHECK_FLAG); + } + if (pszPassword != IntPtr.Zero) + { + Marshal.FreeHGlobal(pszPassword); + } + if (pfxDataHandle.IsAllocated) + { + pfxDataHandle.Free(); + } + if (phProv != IntPtr.Zero) + { + Native.CryptReleaseContext(phProv, 0u); + } + } + + return result; + } + } +} + +"@ + +Add-Type -ReferencedAssemblies $Assem -TypeDefinition $Source -Language CSharp + +function Ensure-Login () +{ + $context = Get-AzureRmContext + If($context.Subscription -eq $null) + { + Write-Host "Loging in ..." + If((Login-AzureRmAccount -ErrorAction SilentlyContinue -ErrorVariable Errors) -eq $null) + { + Write-Host ("Login failed: {0}" -f $Errors[0].Exception.Message) -ForegroundColor Red + Break + } + } + Write-Host "User logedin." -ForegroundColor Green +} + +function Select-SubscriptionId { + param ( + $subscriptionId + ) + Write-Host "Selecting subscription '$subscriptionId'." + $context = Get-AzureRmContext + If($context.Subscription.Id -ne $subscriptionId) + { + Try + { + Select-AzureRmSubscription -SubscriptionId $subscriptionId -ErrorAction Stop | Out-null + } + Catch + { + Write-Host "Subscription selection failed: $_" -ForegroundColor Red + Break + } + } + Write-Host "Subscription selected." -ForegroundColor Green +} + +function Verify-PSVersion +{ + Write-Host "Verifying PowerShell version, must be 5.0 or higher." + if($PSVersionTable.PSVersion.Major -ge 5) + { + Write-Host "PowerShell version verified." -ForegroundColor Green + } + else + { + Write-Host "You need to install PowerShell version 5.0 or heigher." -ForegroundColor Red + Break; + } +} + +function Ensure-SqlModule +{ + function Verify-SqlModule + { + param ($module) + + Write-Host "Verifying if AzureRM.Sql module version, must be 4.10.0.0 or higher." + if( + ($module.Version.Major -gt 4) -or + (($module.Version.Major -eq 4) -and ($module.Version.Minor -ge 10)) + ) + { + Write-Host "Module AzureRM.Sql verified." -ForegroundColor Green + } + else + { + Write-Host "Trying to update AzureRM.Sql module." + try + { + Update-Module AzureRM.Sql -ErrorAction Stop | Out-null + Write-Host "AzureRM.Sql module updated, you need to restart session for changes to be applied." -ForegroundColor Yellow + } + catch + { + Write-Host "Update module failed: $_" -ForegroundColor Yellow + Write-Host "Trying to install AzureRM.Sql module." + try + { + Install-Module AzureRM.Sql -Force -ErrorAction Stop | Out-null + Write-Host "AzureRM.Sql module installed, you need to restart session for changes to be applied." -ForegroundColor Yellow + } + catch + { + Write-Host "Install module failed: $_" -ForegroundColor Red + } + } + Break; + } + } + + Write-Host "Checking if AzureRM.Sql module is imported." + $module = Get-Module AzureRM.Sql + If($module -eq $null) + { + Import-Module AzureRM.Sql + $module = Get-Module AzureRM.Sql + Write-Host "Module AzureRM.Sql imported." -ForegroundColor Green + } + Verify-SqlModule $module +} + +Verify-PSVersion +Ensure-SqlModule + +Ensure-Login + +Select-SubscriptionId -subscriptionId $subscriptionId + +Write-Host "Loading .cer file." + +$publicKeyBytes = Get-Content $publicKeyFile -Encoding Byte +$base64EncodedPublicKey = [System.Convert]::ToBase64String($publicKeyBytes) + +Write-Host "Loading .pvk file." + +$privateKeyBytes = Get-Content $privateKeyFile -Encoding Byte +$base64EncodedPrivateKey = [System.Convert]::ToBase64String($privateKeyBytes) + +Write-Host "Creating .pfx blob." + +$base64EncodedCert = [CL.CertUtil]::CerPvkToPfx($base64EncodedPublicKey, $base64EncodedPrivateKey, $password) + +$bytes = [Convert]::FromBase64String($base64EncodedCert) +[IO.File]::WriteAllBytes('C:/full_path/testps.pfx', $bytes) + +$securePrivateBlob = $base64EncodedCert | ConvertTo-SecureString -AsPlainText -Force +$securePassword = $password | ConvertTo-SecureString -AsPlainText -Force + +Write-Host "Adding TDE certificate." + + +Try +{ + Add-AzureRmSqlManagedInstanceTransparentDataEncryptionCertificate -ResourceGroupName $resourceGroupName -ManagedInstanceName $managedInstanceName -PrivateBlob $securePrivateBlob -Password $securePassword -ErrorAction Stop | Out-Null + Write-Host "TDE certificate added." -ForegroundColor Green +} +Catch +{ + Write-Host "Failed: $_" -ForegroundColor Red +} + + + + + +