diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java index 4aa38ec77..78db017d7 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java @@ -24,7 +24,6 @@ import java.util.logging.Level; import com.azure.core.credential.TokenCredential; -import com.azure.identity.ClientSecretCredentialBuilder; import com.azure.identity.ManagedIdentityCredentialBuilder; import com.azure.security.keyvault.keys.KeyClient; import com.azure.security.keyvault.keys.KeyClientBuilder; @@ -39,6 +38,7 @@ import com.azure.security.keyvault.keys.models.KeyType; import com.azure.security.keyvault.keys.models.KeyVaultKey; + /** * Provides implementation similar to certificate store provider. A CEK encrypted with certificate store provider should * be decryptable by this provider and vice versa. @@ -51,675 +51,640 @@ */ public class SQLServerColumnEncryptionAzureKeyVaultProvider extends SQLServerColumnEncryptionKeyStoreProvider { - private final static java.util.logging.Logger akvLogger = java.util.logging.Logger - .getLogger("com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionAzureKeyVaultProvider"); - /** - * Column Encryption Key Store Provider string - */ - String name = "AZURE_KEY_VAULT"; - - private static final String MSSQL_JDBC_PROPERTIES = "mssql-jdbc.properties"; - private static final String AKV_TRUSTED_ENDPOINTS_KEYWORD = "AKVTrustedEndpoints"; - private static final String RSA_ENCRYPTION_ALGORITHM_WITH_OAEP_FOR_AKV = "RSA-OAEP"; - - private static final List akvTrustedEndpoints; - static { - akvTrustedEndpoints = getTrustedEndpoints(); - } - - /** - * Algorithm version - */ - private final byte[] firstVersion = new byte[] {0x01}; - - private CryptographyClient cryptoClient; - private KeyClient keyClient; - private TokenCredential credential; - - public void setName(String name) { - this.name = name; - } - - public String getName() { - return this.name; - } - - public SQLServerColumnEncryptionAzureKeyVaultProvider(String clientId, String clientKey) throws SQLServerException { - KeyVaultCredential keyVaultCredential = new KeyVaultCredential(clientId, clientKey); - HttpPipeline pipeline = new KeyVaultHttpPipelineBuilder() - .credential(keyVaultCredential) - .buildPipeline(); - - keyClient = new KeyClientBuilder() - .pipeline(pipeline) - .vaultUrl("https://susanakv.vault.azure.net") - .buildClient(); - } - - /** - * Constructs a SQLServerColumnEncryptionAzureKeyVaultProvider with a client id and client key to authenticate to - * AAD. This is used by KeyVaultClient at runtime to authenticate to Azure Key Vault. - * - * @param clientId - * Identifier of the client requesting the token. - * @param clientKey - * Key of the client requesting the token. - * @param tenantId - * The tenant ID of the application. - * @throws SQLServerException - * when an error occurs - */ - public SQLServerColumnEncryptionAzureKeyVaultProvider(String clientId, - String clientKey, - String tenantId) throws SQLServerException { - if (clientId == null || clientId.isEmpty()) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NullValue")); - Object[] msgArgs1 = {"Client ID"}; - throw new SQLServerException(form.format(msgArgs1), null); - } - if (clientKey == null || clientKey.isEmpty()) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NullValue")); - Object[] msgArgs1 = {"Client Key"}; - throw new SQLServerException(form.format(msgArgs1), null); + private final static java.util.logging.Logger akvLogger = java.util.logging.Logger + .getLogger("com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionAzureKeyVaultProvider"); + /** + * Column Encryption Key Store Provider string + */ + String name = "AZURE_KEY_VAULT"; + + private static final String MSSQL_JDBC_PROPERTIES = "mssql-jdbc.properties"; + private static final String AKV_TRUSTED_ENDPOINTS_KEYWORD = "AKVTrustedEndpoints"; + private static final String RSA_ENCRYPTION_ALGORITHM_WITH_OAEP_FOR_AKV = "RSA-OAEP"; + + private static final List akvTrustedEndpoints; + + static { + akvTrustedEndpoints = getTrustedEndpoints(); } - createKeyvaultClients(new ClientSecretCredentialBuilder() - .clientId(clientId) - .clientSecret(clientKey) - .tenantId(tenantId) - .build()); - } - - /** - * Constructs a SQLServerColumnEncryptionAzureKeyVaultProvider to authenticate to AAD. This is used by - * KeyVaultClient at runtime to authenticate to Azure Key Vault. - */ - SQLServerColumnEncryptionAzureKeyVaultProvider() throws SQLServerException { - createKeyvaultClients(new ManagedIdentityCredentialBuilder().build()); - } - - /** - * Constructs a SQLServerColumnEncryptionAzureKeyVaultProvider to authenticate to AAD. This is used by - * KeyVaultClient at runtime to authenticate to Azure Key Vault. - * - * @param clientId - * Identifier of the client requesting the token. - */ - SQLServerColumnEncryptionAzureKeyVaultProvider(String clientId) throws SQLServerException { - if (clientId == null || clientId.isEmpty()) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NullValue")); - Object[] msgArgs1 = {"Client ID"}; - throw new SQLServerException(form.format(msgArgs1), null); + + /** + * Algorithm version + */ + private final byte[] firstVersion = new byte[] {0x01}; + + private CryptographyClient cryptoClient; + private KeyClient keyClient; + private TokenCredential credential; + + public void setName(String name) { + this.name = name; } - createKeyvaultClients(new ManagedIdentityCredentialBuilder().clientId(clientId).build()); - } - - /** - * Constructs a SQLServerColumnEncryptionAzureKeyVaultProvider using the provided TokenCredential to authenticate to - * AAD. This is used by KeyVaultClient at runtime to authenticate to Azure Key Vault. - * - * @param tokenCredential - * The TokenCredential to use to authenticate to Azure Key Vault. - */ - public SQLServerColumnEncryptionAzureKeyVaultProvider(TokenCredential tokenCredential) throws SQLServerException { - createKeyvaultClients(tokenCredential); - } - - private void createKeyvaultClients(TokenCredential credential) throws SQLServerException { - String vaultBaseUrl = "susanakv.vault.azure.net"; //System.getenv("vaultBaseUrl"); - String vaultFullUrl = "https://" + vaultBaseUrl; - - if (null == vaultBaseUrl || vaultBaseUrl.isEmpty()) { - // TODO externalise string -// throw new SQLServerException(SQLServerException.getErrString("R_NullEncryptedColumnEncryptionKey"), null); - throw new SQLServerException("vaultBaseUrl is not valid: " + vaultBaseUrl, null); + + public String getName() { + return this.name; } - this.credential = credential; - - this.keyClient = new KeyClientBuilder() - .credential(credential) - .vaultUrl(vaultFullUrl) - .buildClient(); - } - - /** - * Decrypts an encrypted CEK with RSA encryption algorithm using the asymmetric key specified by the key path - * - * @param masterKeyPath - * - Complete path of an asymmetric key in AKV - * @param encryptionAlgorithm - * - Asymmetric Key Encryption Algorithm - * @param encryptedColumnEncryptionKey - * - Encrypted Column Encryption Key - * @return Plain text column encryption key - */ - @Override - public byte[] decryptColumnEncryptionKey(String masterKeyPath, String encryptionAlgorithm, - byte[] encryptedColumnEncryptionKey) throws SQLServerException { - - // Validate the input parameters - this.ValidateNonEmptyAKVPath(masterKeyPath); - - if (null == encryptedColumnEncryptionKey) { - throw new SQLServerException(SQLServerException.getErrString("R_NullEncryptedColumnEncryptionKey"), null); + public SQLServerColumnEncryptionAzureKeyVaultProvider(String clientId, String clientKey) + throws SQLServerException { + if (clientId == null || clientId.isEmpty()) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NullValue")); + Object[] msgArgs1 = {"Client ID"}; + throw new SQLServerException(form.format(msgArgs1), null); + } + if (clientKey == null || clientKey.isEmpty()) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NullValue")); + Object[] msgArgs1 = {"Client Key"}; + throw new SQLServerException(form.format(msgArgs1), null); + } + + KeyVaultCredential keyVaultCredential = new KeyVaultCredential(clientId, clientKey); + HttpPipeline pipeline = new KeyVaultHttpPipelineBuilder().credential(keyVaultCredential) + .buildPipeline(); + + keyClient = new KeyClientBuilder().pipeline(pipeline).vaultUrl("https://susanakv.vault.azure.net") + .buildClient(); } - if (0 == encryptedColumnEncryptionKey.length) { - throw new SQLServerException(SQLServerException.getErrString("R_EmptyEncryptedColumnEncryptionKey"), null); + /** + * Constructs a SQLServerColumnEncryptionAzureKeyVaultProvider to authenticate to AAD. This is used by + * KeyVaultClient at runtime to authenticate to Azure Key Vault. + */ + SQLServerColumnEncryptionAzureKeyVaultProvider() throws SQLServerException { + createKeyvaultClients(new ManagedIdentityCredentialBuilder().build()); } - // Validate encryptionAlgorithm - KeyWrapAlgorithm _encryptionAlgorithm = this.validateEncryptionAlgorithm(encryptionAlgorithm); - - // Validate whether the key is RSA one or not and then get the key size - int keySizeInBytes = getAKVKeySize(masterKeyPath); - - // Validate and decrypt the EncryptedColumnEncryptionKey - // Format is - // version + keyPathLength + ciphertextLength + keyPath + ciphertext + signature - // - // keyPath is present in the encrypted column encryption key for identifying the original source of the - // asymmetric key pair and - // we will not validate it against the data contained in the CMK metadata (masterKeyPath). - - // Validate the version byte - if (encryptedColumnEncryptionKey[0] != firstVersion[0]) { - MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_InvalidEcryptionAlgorithmVersion")); - Object[] msgArgs = {String.format("%02X ", encryptedColumnEncryptionKey[0]), - String.format("%02X ", firstVersion[0])}; - throw new SQLServerException(this, form.format(msgArgs), null, 0, false); + /** + * Constructs a SQLServerColumnEncryptionAzureKeyVaultProvider to authenticate to AAD. This is used by + * KeyVaultClient at runtime to authenticate to Azure Key Vault. + * + * @param clientId Identifier of the client requesting the token. + */ + SQLServerColumnEncryptionAzureKeyVaultProvider(String clientId) throws SQLServerException { + if (clientId == null || clientId.isEmpty()) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NullValue")); + Object[] msgArgs1 = {"Client ID"}; + throw new SQLServerException(form.format(msgArgs1), null); + } + createKeyvaultClients(new ManagedIdentityCredentialBuilder().clientId(clientId).build()); } - // Get key path length - int currentIndex = firstVersion.length; - short keyPathLength = convertTwoBytesToShort(encryptedColumnEncryptionKey, currentIndex); - // We just read 2 bytes - currentIndex += 2; - - // Get ciphertext length - short cipherTextLength = convertTwoBytesToShort(encryptedColumnEncryptionKey, currentIndex); - currentIndex += 2; - - // Skip KeyPath - // KeyPath exists only for troubleshooting purposes and doesnt need validation. - currentIndex += keyPathLength; - - // validate the ciphertext length - if (cipherTextLength != keySizeInBytes) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AKVKeyLengthError")); - Object[] msgArgs = {cipherTextLength, keySizeInBytes, masterKeyPath}; - throw new SQLServerException(this, form.format(msgArgs), null, 0, false); + /** + * Constructs a SQLServerColumnEncryptionAzureKeyVaultProvider using the provided TokenCredential to + * authenticate to AAD. This is used by KeyVaultClient at runtime to authenticate to Azure Key Vault. + * + * @param tokenCredential The TokenCredential to use to authenticate to Azure Key Vault. + */ + public SQLServerColumnEncryptionAzureKeyVaultProvider(TokenCredential tokenCredential) + throws SQLServerException { + createKeyvaultClients(tokenCredential); } - // Validate the signature length - int signatureLength = encryptedColumnEncryptionKey.length - currentIndex - cipherTextLength; + private void createKeyvaultClients(TokenCredential credential) throws SQLServerException { + String vaultBaseUrl = "susanakv.vault.azure.net"; //System.getenv("vaultBaseUrl"); + String vaultFullUrl = "https://" + vaultBaseUrl; + + if (null == vaultBaseUrl || vaultBaseUrl.isEmpty()) { + // TODO externalise string + // throw new SQLServerException(SQLServerException.getErrString("R_NullEncryptedColumnEncryptionKey"), null); + throw new SQLServerException("vaultBaseUrl is not valid: " + vaultBaseUrl, null); + } - if (signatureLength != keySizeInBytes) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AKVSignatureLengthError")); - Object[] msgArgs = {signatureLength, keySizeInBytes, masterKeyPath}; - throw new SQLServerException(this, form.format(msgArgs), null, 0, false); + this.credential = credential; + + this.keyClient = new KeyClientBuilder().credential(credential).vaultUrl(vaultFullUrl).buildClient(); } - // Get ciphertext - byte[] cipherText = new byte[cipherTextLength]; - System.arraycopy(encryptedColumnEncryptionKey, currentIndex, cipherText, 0, cipherTextLength); - currentIndex += cipherTextLength; + /** + * Decrypts an encrypted CEK with RSA encryption algorithm using the asymmetric key specified by the key path + * + * @param masterKeyPath - Complete path of an asymmetric key in AKV + * @param encryptionAlgorithm - Asymmetric Key Encryption Algorithm + * @param encryptedColumnEncryptionKey - Encrypted Column Encryption Key + * @return Plain text column encryption key + */ + @Override public byte[] decryptColumnEncryptionKey(String masterKeyPath, String encryptionAlgorithm, + byte[] encryptedColumnEncryptionKey) throws SQLServerException { - // Get signature - byte[] signature = new byte[signatureLength]; - System.arraycopy(encryptedColumnEncryptionKey, currentIndex, signature, 0, signatureLength); + // Validate the input parameters + this.ValidateNonEmptyAKVPath(masterKeyPath); - // Compute the hash to validate the signature - byte[] hash = new byte[encryptedColumnEncryptionKey.length - signature.length]; + if (null == encryptedColumnEncryptionKey) { + throw new SQLServerException( + SQLServerException.getErrString("R_NullEncryptedColumnEncryptionKey"), null); + } - System.arraycopy(encryptedColumnEncryptionKey, 0, hash, 0, - encryptedColumnEncryptionKey.length - signature.length); + if (0 == encryptedColumnEncryptionKey.length) { + throw new SQLServerException( + SQLServerException.getErrString("R_EmptyEncryptedColumnEncryptionKey"), null); + } - MessageDigest md = null; - try { - md = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), e); - } - md.update(hash); - byte dataToVerify[] = md.digest(); + // Validate encryptionAlgorithm + KeyWrapAlgorithm _encryptionAlgorithm = this.validateEncryptionAlgorithm(encryptionAlgorithm); + + // Validate whether the key is RSA one or not and then get the key size + int keySizeInBytes = getAKVKeySize(masterKeyPath); + + // Validate and decrypt the EncryptedColumnEncryptionKey + // Format is + // version + keyPathLength + ciphertextLength + keyPath + ciphertext + signature + // + // keyPath is present in the encrypted column encryption key for identifying the original source of the + // asymmetric key pair and + // we will not validate it against the data contained in the CMK metadata (masterKeyPath). + + // Validate the version byte + if (encryptedColumnEncryptionKey[0] != firstVersion[0]) { + MessageFormat form = new MessageFormat( + SQLServerException.getErrString("R_InvalidEcryptionAlgorithmVersion")); + Object[] msgArgs = {String.format("%02X ", encryptedColumnEncryptionKey[0]), + String.format("%02X ", firstVersion[0])}; + throw new SQLServerException(this, form.format(msgArgs), null, 0, false); + } - if (null == dataToVerify) { - throw new SQLServerException(SQLServerException.getErrString("R_HashNull"), null); - } + // Get key path length + int currentIndex = firstVersion.length; + short keyPathLength = convertTwoBytesToShort(encryptedColumnEncryptionKey, currentIndex); + // We just read 2 bytes + currentIndex += 2; + + // Get ciphertext length + short cipherTextLength = convertTwoBytesToShort(encryptedColumnEncryptionKey, currentIndex); + currentIndex += 2; + + // Skip KeyPath + // KeyPath exists only for troubleshooting purposes and doesnt need validation. + currentIndex += keyPathLength; + + // validate the ciphertext length + if (cipherTextLength != keySizeInBytes) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AKVKeyLengthError")); + Object[] msgArgs = {cipherTextLength, keySizeInBytes, masterKeyPath}; + throw new SQLServerException(this, form.format(msgArgs), null, 0, false); + } - // Validate the signature - if (!AzureKeyVaultVerifySignature(dataToVerify, signature, masterKeyPath)) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_CEKSignatureNotMatchCMK")); - Object[] msgArgs = {masterKeyPath}; - throw new SQLServerException(this, form.format(msgArgs), null, 0, false); - } + // Validate the signature length + int signatureLength = encryptedColumnEncryptionKey.length - currentIndex - cipherTextLength; - // Decrypt the CEK - byte[] decryptedCEK = this.AzureKeyVaultUnWrap(masterKeyPath, _encryptionAlgorithm, cipherText); + if (signatureLength != keySizeInBytes) { + MessageFormat form = new MessageFormat( + SQLServerException.getErrString("R_AKVSignatureLengthError")); + Object[] msgArgs = {signatureLength, keySizeInBytes, masterKeyPath}; + throw new SQLServerException(this, form.format(msgArgs), null, 0, false); + } - return decryptedCEK; - } + // Get ciphertext + byte[] cipherText = new byte[cipherTextLength]; + System.arraycopy(encryptedColumnEncryptionKey, currentIndex, cipherText, 0, cipherTextLength); + currentIndex += cipherTextLength; - private short convertTwoBytesToShort(byte[] input, int index) throws SQLServerException { + // Get signature + byte[] signature = new byte[signatureLength]; + System.arraycopy(encryptedColumnEncryptionKey, currentIndex, signature, 0, signatureLength); - short shortVal; - if (index + 1 >= input.length) { - throw new SQLServerException(null, SQLServerException.getErrString("R_ByteToShortConversion"), null, 0, - false); - } - ByteBuffer byteBuffer = ByteBuffer.allocate(2); - byteBuffer.order(ByteOrder.LITTLE_ENDIAN); - byteBuffer.put(input[index]); - byteBuffer.put(input[index + 1]); - shortVal = byteBuffer.getShort(0); - return shortVal; - - } - - /** - * Encrypts CEK with RSA encryption algorithm using the asymmetric key specified by the key path. - * - * @param masterKeyPath - * - Complete path of an asymmetric key in AKV - * @param encryptionAlgorithm - * - Asymmetric Key Encryption Algorithm - * @param columnEncryptionKey - * - Plain text column encryption key - * @return Encrypted column encryption key - */ - @Override - public byte[] encryptColumnEncryptionKey(String masterKeyPath, String encryptionAlgorithm, - byte[] columnEncryptionKey) throws SQLServerException { - - // Validate the input parameters - this.ValidateNonEmptyAKVPath(masterKeyPath); - - if (null == columnEncryptionKey) { - throw new SQLServerException(SQLServerException.getErrString("R_NullColumnEncryptionKey"), null); - } + // Compute the hash to validate the signature + byte[] hash = new byte[encryptedColumnEncryptionKey.length - signature.length]; + + System.arraycopy(encryptedColumnEncryptionKey, 0, hash, 0, + encryptedColumnEncryptionKey.length - signature.length); + + MessageDigest md = null; + try { + md = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), e); + } + md.update(hash); + byte dataToVerify[] = md.digest(); + + if (null == dataToVerify) { + throw new SQLServerException(SQLServerException.getErrString("R_HashNull"), null); + } + + // Validate the signature + if (!AzureKeyVaultVerifySignature(dataToVerify, signature, masterKeyPath)) { + MessageFormat form = new MessageFormat( + SQLServerException.getErrString("R_CEKSignatureNotMatchCMK")); + Object[] msgArgs = {masterKeyPath}; + throw new SQLServerException(this, form.format(msgArgs), null, 0, false); + } - if (0 == columnEncryptionKey.length) { - throw new SQLServerException(SQLServerException.getErrString("R_EmptyCEK"), null); + // Decrypt the CEK + byte[] decryptedCEK = this.AzureKeyVaultUnWrap(masterKeyPath, _encryptionAlgorithm, cipherText); + + return decryptedCEK; } - // Validate encryptionAlgorithm - KeyWrapAlgorithm _encryptionAlgorithm = this.validateEncryptionAlgorithm(encryptionAlgorithm); + private short convertTwoBytesToShort(byte[] input, int index) throws SQLServerException { - // Validate whether the key is RSA one or not and then get the key size - int keySizeInBytes = getAKVKeySize(masterKeyPath); + short shortVal; + if (index + 1 >= input.length) { + throw new SQLServerException(null, SQLServerException.getErrString("R_ByteToShortConversion"), + null, 0, false); + } + ByteBuffer byteBuffer = ByteBuffer.allocate(2); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + byteBuffer.put(input[index]); + byteBuffer.put(input[index + 1]); + shortVal = byteBuffer.getShort(0); + return shortVal; + + } + + /** + * Encrypts CEK with RSA encryption algorithm using the asymmetric key specified by the key path. + * + * @param masterKeyPath - Complete path of an asymmetric key in AKV + * @param encryptionAlgorithm - Asymmetric Key Encryption Algorithm + * @param columnEncryptionKey - Plain text column encryption key + * @return Encrypted column encryption key + */ + @Override public byte[] encryptColumnEncryptionKey(String masterKeyPath, String encryptionAlgorithm, + byte[] columnEncryptionKey) throws SQLServerException { - // Construct the encryptedColumnEncryptionKey - // Format is - // version + keyPathLength + ciphertextLength + ciphertext + keyPath + signature - // - // We currently only support one version - byte[] version = new byte[] {firstVersion[0]}; + // Validate the input parameters + this.ValidateNonEmptyAKVPath(masterKeyPath); - // Get the Unicode encoded bytes of cultureinvariant lower case masterKeyPath - byte[] masterKeyPathBytes = masterKeyPath.toLowerCase(Locale.ENGLISH).getBytes(UTF_16LE); + if (null == columnEncryptionKey) { + throw new SQLServerException(SQLServerException.getErrString("R_NullColumnEncryptionKey"), + null); + } - byte[] keyPathLength = new byte[2]; - keyPathLength[0] = (byte) (((short) masterKeyPathBytes.length) & 0xff); - keyPathLength[1] = (byte) (((short) masterKeyPathBytes.length) >> 8 & 0xff); + if (0 == columnEncryptionKey.length) { + throw new SQLServerException(SQLServerException.getErrString("R_EmptyCEK"), null); + } - // Encrypt the plain text - byte[] cipherText = this.AzureKeyVaultWrap(masterKeyPath, _encryptionAlgorithm, columnEncryptionKey); + // Validate encryptionAlgorithm + KeyWrapAlgorithm _encryptionAlgorithm = this.validateEncryptionAlgorithm(encryptionAlgorithm); - byte[] cipherTextLength = new byte[2]; - cipherTextLength[0] = (byte) (((short) cipherText.length) & 0xff); - cipherTextLength[1] = (byte) (((short) cipherText.length) >> 8 & 0xff); + // Validate whether the key is RSA one or not and then get the key size + int keySizeInBytes = getAKVKeySize(masterKeyPath); - if (cipherText.length != keySizeInBytes) { - throw new SQLServerException(SQLServerException.getErrString("R_CipherTextLengthNotMatchRSASize"), null); - } + // Construct the encryptedColumnEncryptionKey + // Format is + // version + keyPathLength + ciphertextLength + ciphertext + keyPath + signature + // + // We currently only support one version + byte[] version = new byte[] {firstVersion[0]}; - // Compute hash - // SHA-2-256(version + keyPathLength + ciphertextLength + keyPath + ciphertext) - byte[] dataToHash = new byte[version.length + keyPathLength.length + cipherTextLength.length - + masterKeyPathBytes.length + cipherText.length]; - int destinationPosition = version.length; - System.arraycopy(version, 0, dataToHash, 0, version.length); + // Get the Unicode encoded bytes of cultureinvariant lower case masterKeyPath + byte[] masterKeyPathBytes = masterKeyPath.toLowerCase(Locale.ENGLISH).getBytes(UTF_16LE); - System.arraycopy(keyPathLength, 0, dataToHash, destinationPosition, keyPathLength.length); - destinationPosition += keyPathLength.length; + byte[] keyPathLength = new byte[2]; + keyPathLength[0] = (byte) (((short) masterKeyPathBytes.length) & 0xff); + keyPathLength[1] = (byte) (((short) masterKeyPathBytes.length) >> 8 & 0xff); - System.arraycopy(cipherTextLength, 0, dataToHash, destinationPosition, cipherTextLength.length); - destinationPosition += cipherTextLength.length; + // Encrypt the plain text + byte[] cipherText = this.AzureKeyVaultWrap(masterKeyPath, _encryptionAlgorithm, columnEncryptionKey); - System.arraycopy(masterKeyPathBytes, 0, dataToHash, destinationPosition, masterKeyPathBytes.length); - destinationPosition += masterKeyPathBytes.length; + byte[] cipherTextLength = new byte[2]; + cipherTextLength[0] = (byte) (((short) cipherText.length) & 0xff); + cipherTextLength[1] = (byte) (((short) cipherText.length) >> 8 & 0xff); - System.arraycopy(cipherText, 0, dataToHash, destinationPosition, cipherText.length); + if (cipherText.length != keySizeInBytes) { + throw new SQLServerException( + SQLServerException.getErrString("R_CipherTextLengthNotMatchRSASize"), null); + } - MessageDigest md = null; - try { - md = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), e); - } - md.update(dataToHash); - byte dataToSign[] = md.digest(); + // Compute hash + // SHA-2-256(version + keyPathLength + ciphertextLength + keyPath + ciphertext) + byte[] dataToHash = new byte[version.length + keyPathLength.length + cipherTextLength.length + + masterKeyPathBytes.length + cipherText.length]; + int destinationPosition = version.length; + System.arraycopy(version, 0, dataToHash, 0, version.length); - // Sign the hash - byte[] signedHash = AzureKeyVaultSignHashedData(dataToSign, masterKeyPath); + System.arraycopy(keyPathLength, 0, dataToHash, destinationPosition, keyPathLength.length); + destinationPosition += keyPathLength.length; - if (signedHash.length != keySizeInBytes) { - throw new SQLServerException(SQLServerException.getErrString("R_SignedHashLengthError"), null); - } + System.arraycopy(cipherTextLength, 0, dataToHash, destinationPosition, cipherTextLength.length); + destinationPosition += cipherTextLength.length; - if (!this.AzureKeyVaultVerifySignature(dataToSign, signedHash, masterKeyPath)) { - throw new SQLServerException(SQLServerException.getErrString("R_InvalidSignatureComputed"), null); - } + System.arraycopy(masterKeyPathBytes, 0, dataToHash, destinationPosition, masterKeyPathBytes.length); + destinationPosition += masterKeyPathBytes.length; - // Construct the encrypted column encryption key - // EncryptedColumnEncryptionKey = version + keyPathLength + ciphertextLength + keyPath + ciphertext + signature - int encryptedColumnEncryptionKeyLength = version.length + cipherTextLength.length + keyPathLength.length - + cipherText.length + masterKeyPathBytes.length + signedHash.length; - byte[] encryptedColumnEncryptionKey = new byte[encryptedColumnEncryptionKeyLength]; - - // Copy version byte - int currentIndex = 0; - System.arraycopy(version, 0, encryptedColumnEncryptionKey, currentIndex, version.length); - currentIndex += version.length; - - // Copy key path length - System.arraycopy(keyPathLength, 0, encryptedColumnEncryptionKey, currentIndex, keyPathLength.length); - currentIndex += keyPathLength.length; - - // Copy ciphertext length - System.arraycopy(cipherTextLength, 0, encryptedColumnEncryptionKey, currentIndex, cipherTextLength.length); - currentIndex += cipherTextLength.length; - - // Copy key path - System.arraycopy(masterKeyPathBytes, 0, encryptedColumnEncryptionKey, currentIndex, masterKeyPathBytes.length); - currentIndex += masterKeyPathBytes.length; - - // Copy ciphertext - System.arraycopy(cipherText, 0, encryptedColumnEncryptionKey, currentIndex, cipherText.length); - currentIndex += cipherText.length; - - // copy the signature - System.arraycopy(signedHash, 0, encryptedColumnEncryptionKey, currentIndex, signedHash.length); - - return encryptedColumnEncryptionKey; - } - - /** - * Validates that the encryption algorithm is RSA_OAEP and if it is not, then throws an exception. - * - * @param encryptionAlgorithm - * - Asymmetric key encryptio algorithm - * @return The encryption algorithm that is going to be used. - * @throws SQLServerException - */ - private KeyWrapAlgorithm validateEncryptionAlgorithm(String encryptionAlgorithm) throws SQLServerException { - - if (null == encryptionAlgorithm) { - throw new SQLServerException(null, SQLServerException.getErrString("R_NullKeyEncryptionAlgorithm"), null, 0, - false); - } + System.arraycopy(cipherText, 0, dataToHash, destinationPosition, cipherText.length); - // Transform to standard format (dash instead of underscore) to support enum lookup - if ("RSA_OAEP".equalsIgnoreCase(encryptionAlgorithm)) { - encryptionAlgorithm = RSA_ENCRYPTION_ALGORITHM_WITH_OAEP_FOR_AKV; - } + MessageDigest md = null; + try { + md = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), e); + } + md.update(dataToHash); + byte dataToSign[] = md.digest(); - if (!RSA_ENCRYPTION_ALGORITHM_WITH_OAEP_FOR_AKV.equalsIgnoreCase(encryptionAlgorithm.trim())) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidKeyEncryptionAlgorithm")); - Object[] msgArgs = {encryptionAlgorithm, RSA_ENCRYPTION_ALGORITHM_WITH_OAEP_FOR_AKV}; - throw new SQLServerException(this, form.format(msgArgs), null, 0, false); - } + // Sign the hash + byte[] signedHash = AzureKeyVaultSignHashedData(dataToSign, masterKeyPath); - return KeyWrapAlgorithm.fromString(encryptionAlgorithm); - } - - /** - * Checks if the Azure Key Vault key path is Empty or Null (and raises exception if they are). - * - * @param masterKeyPath - * @throws SQLServerException - */ - private void ValidateNonEmptyAKVPath(String masterKeyPath) throws SQLServerException { - // throw appropriate error if masterKeyPath is null or empty - if (null == masterKeyPath || masterKeyPath.trim().isEmpty()) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AKVPathNull")); - Object[] msgArgs = {masterKeyPath}; - throw new SQLServerException(null, form.format(msgArgs), null, 0, false); - } else { - URI parsedUri = null; - try { - parsedUri = new URI(masterKeyPath); - - // A valid URI. - // Check if it is pointing to a trusted endpoint. - String host = parsedUri.getHost(); - if (null != host) { - host = host.toLowerCase(Locale.ENGLISH); - } - for (final String endpoint : akvTrustedEndpoints) { - if (null != host && host.endsWith(endpoint)) { - return; - } - } - } catch (URISyntaxException e) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AKVURLInvalid")); - Object[] msgArgs = {masterKeyPath}; - throw new SQLServerException(form.format(msgArgs), null, 0, e); - } + if (signedHash.length != keySizeInBytes) { + throw new SQLServerException(SQLServerException.getErrString("R_SignedHashLengthError"), null); + } - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AKVMasterKeyPathInvalid")); - Object[] msgArgs = {masterKeyPath}; - throw new SQLServerException(null, form.format(msgArgs), null, 0, false); - } - } - - /** - * Encrypts the text using specified Azure Key Vault key. - * - * @param masterKeyPath - * - Azure Key Vault key url. - * @param encryptionAlgorithm - * - Encryption Algorithm. - * @param columnEncryptionKey - * - Plain text Column Encryption Key. - * @return Returns an encrypted blob or throws an exception if there are any errors. - * @throws SQLServerException - */ - private byte[] AzureKeyVaultWrap(String masterKeyPath, KeyWrapAlgorithm encryptionAlgorithm, - byte[] columnEncryptionKey) throws SQLServerException { - if (null == columnEncryptionKey) { - throw new SQLServerException(SQLServerException.getErrString("R_CEKNull"), null); + if (!this.AzureKeyVaultVerifySignature(dataToSign, signedHash, masterKeyPath)) { + throw new SQLServerException(SQLServerException.getErrString("R_InvalidSignatureComputed"), + null); + } + + // Construct the encrypted column encryption key + // EncryptedColumnEncryptionKey = version + keyPathLength + ciphertextLength + keyPath + ciphertext + signature + int encryptedColumnEncryptionKeyLength = + version.length + cipherTextLength.length + keyPathLength.length + cipherText.length + + masterKeyPathBytes.length + signedHash.length; + byte[] encryptedColumnEncryptionKey = new byte[encryptedColumnEncryptionKeyLength]; + + // Copy version byte + int currentIndex = 0; + System.arraycopy(version, 0, encryptedColumnEncryptionKey, currentIndex, version.length); + currentIndex += version.length; + + // Copy key path length + System.arraycopy(keyPathLength, 0, encryptedColumnEncryptionKey, currentIndex, keyPathLength.length); + currentIndex += keyPathLength.length; + + // Copy ciphertext length + System.arraycopy(cipherTextLength, 0, encryptedColumnEncryptionKey, currentIndex, + cipherTextLength.length); + currentIndex += cipherTextLength.length; + + // Copy key path + System.arraycopy(masterKeyPathBytes, 0, encryptedColumnEncryptionKey, currentIndex, + masterKeyPathBytes.length); + currentIndex += masterKeyPathBytes.length; + + // Copy ciphertext + System.arraycopy(cipherText, 0, encryptedColumnEncryptionKey, currentIndex, cipherText.length); + currentIndex += cipherText.length; + + // copy the signature + System.arraycopy(signedHash, 0, encryptedColumnEncryptionKey, currentIndex, signedHash.length); + + return encryptedColumnEncryptionKey; } - WrapResult wrappedKey = cryptoClient.wrapKey(KeyWrapAlgorithm.RSA_OAEP, columnEncryptionKey); - return wrappedKey.getEncryptedKey(); - } - - /** - * Encrypts the text using specified Azure Key Vault key. - * - * @param masterKeyPath - * - Azure Key Vault key url. - * @param encryptionAlgorithm - * - Encrypted Column Encryption Key. - * @param encryptedColumnEncryptionKey - * - Encrypted Column Encryption Key. - * @return Returns the decrypted plaintext Column Encryption Key or throws an exception if there are any errors. - * @throws SQLServerException - */ - private byte[] AzureKeyVaultUnWrap(String masterKeyPath, KeyWrapAlgorithm encryptionAlgorithm, - byte[] encryptedColumnEncryptionKey) throws SQLServerException { - if (null == encryptedColumnEncryptionKey) { - throw new SQLServerException(SQLServerException.getErrString("R_EncryptedCEKNull"), null); + /** + * Validates that the encryption algorithm is RSA_OAEP and if it is not, then throws an exception. + * + * @param encryptionAlgorithm - Asymmetric key encryptio algorithm + * @return The encryption algorithm that is going to be used. + * @throws SQLServerException + */ + private KeyWrapAlgorithm validateEncryptionAlgorithm(String encryptionAlgorithm) throws SQLServerException { + + if (null == encryptionAlgorithm) { + throw new SQLServerException(null, + SQLServerException.getErrString("R_NullKeyEncryptionAlgorithm"), null, 0, false); + } + + // Transform to standard format (dash instead of underscore) to support enum lookup + if ("RSA_OAEP".equalsIgnoreCase(encryptionAlgorithm)) { + encryptionAlgorithm = RSA_ENCRYPTION_ALGORITHM_WITH_OAEP_FOR_AKV; + } + + if (!RSA_ENCRYPTION_ALGORITHM_WITH_OAEP_FOR_AKV.equalsIgnoreCase(encryptionAlgorithm.trim())) { + MessageFormat form = new MessageFormat( + SQLServerException.getErrString("R_InvalidKeyEncryptionAlgorithm")); + Object[] msgArgs = {encryptionAlgorithm, RSA_ENCRYPTION_ALGORITHM_WITH_OAEP_FOR_AKV}; + throw new SQLServerException(this, form.format(msgArgs), null, 0, false); + } + + return KeyWrapAlgorithm.fromString(encryptionAlgorithm); } - if (0 == encryptedColumnEncryptionKey.length) { - throw new SQLServerException(SQLServerException.getErrString("R_EmptyEncryptedCEK"), null); + /** + * Checks if the Azure Key Vault key path is Empty or Null (and raises exception if they are). + * + * @param masterKeyPath + * @throws SQLServerException + */ + private void ValidateNonEmptyAKVPath(String masterKeyPath) throws SQLServerException { + // throw appropriate error if masterKeyPath is null or empty + if (null == masterKeyPath || masterKeyPath.trim().isEmpty()) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AKVPathNull")); + Object[] msgArgs = {masterKeyPath}; + throw new SQLServerException(null, form.format(msgArgs), null, 0, false); + } else { + URI parsedUri = null; + try { + parsedUri = new URI(masterKeyPath); + + // A valid URI. + // Check if it is pointing to a trusted endpoint. + String host = parsedUri.getHost(); + if (null != host) { + host = host.toLowerCase(Locale.ENGLISH); + } + for (final String endpoint : akvTrustedEndpoints) { + if (null != host && host.endsWith(endpoint)) { + return; + } + } + } catch (URISyntaxException e) { + MessageFormat form = new MessageFormat( + SQLServerException.getErrString("R_AKVURLInvalid")); + Object[] msgArgs = {masterKeyPath}; + throw new SQLServerException(form.format(msgArgs), null, 0, e); + } + + MessageFormat form = new MessageFormat( + SQLServerException.getErrString("R_AKVMasterKeyPathInvalid")); + Object[] msgArgs = {masterKeyPath}; + throw new SQLServerException(null, form.format(msgArgs), null, 0, false); + } } - UnwrapResult unwrappedKey = cryptoClient.unwrapKey(encryptionAlgorithm, encryptedColumnEncryptionKey); - - return unwrappedKey.getKey(); - } - - /** - * Generates signature based on RSA PKCS#v1.5 scheme using a specified Azure Key Vault Key URL. - * - * @param dataToSign - * - Text to sign. - * @param masterKeyPath - * - Azure Key Vault key url. - * @return Signature - * @throws SQLServerException - */ - private byte[] AzureKeyVaultSignHashedData(byte[] dataToSign, String masterKeyPath) throws SQLServerException { - assert ((null != dataToSign) && (0 != dataToSign.length)); - - SignResult signedData = cryptoClient.sign(SignatureAlgorithm.RS256, dataToSign); - return signedData.getSignature(); - } - - /** - * Verifies the given RSA PKCSv1.5 signature. - * - * @param dataToVerify - * @param signature - * @param masterKeyPath - * - Azure Key Vault key url. - * @return true if signature is valid, false if it is not valid - * @throws SQLServerException - */ - private boolean AzureKeyVaultVerifySignature(byte[] dataToVerify, byte[] signature, - String masterKeyPath) throws SQLServerException { - assert ((null != dataToVerify) && (0 != dataToVerify.length)); - assert ((null != signature) && (0 != signature.length)); - - VerifyResult valid = cryptoClient.verify(SignatureAlgorithm.RS256, dataToVerify, signature); - - return valid.isValid(); - } - - /** - * Returns the public Key size in bytes. - * - * @param masterKeyPath - * - Azure Key Vault Key path - * @return Key size in bytes - * @throws SQLServerException - * when an error occurs - */ - private int getAKVKeySize(String masterKeyPath) throws SQLServerException { - String[] keyTokens = masterKeyPath.split("/"); - String keyName = keyTokens[keyTokens.length - 2]; - KeyVaultKey retrievedKey = keyClient.getKey(keyName); - - if (null == retrievedKey) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AKVKeyNotFound")); - Object[] msgArgs = {keyTokens[keyTokens.length - 1]}; - throw new SQLServerException(null, form.format(msgArgs), null, 0, false); + /** + * Encrypts the text using specified Azure Key Vault key. + * + * @param masterKeyPath - Azure Key Vault key url. + * @param encryptionAlgorithm - Encryption Algorithm. + * @param columnEncryptionKey - Plain text Column Encryption Key. + * @return Returns an encrypted blob or throws an exception if there are any errors. + * @throws SQLServerException + */ + private byte[] AzureKeyVaultWrap(String masterKeyPath, KeyWrapAlgorithm encryptionAlgorithm, + byte[] columnEncryptionKey) throws SQLServerException { + if (null == columnEncryptionKey) { + throw new SQLServerException(SQLServerException.getErrString("R_CEKNull"), null); + } + + WrapResult wrappedKey = cryptoClient.wrapKey(KeyWrapAlgorithm.RSA_OAEP, columnEncryptionKey); + return wrappedKey.getEncryptedKey(); } - if (retrievedKey.getKeyType() != KeyType.RSA && retrievedKey.getKeyType() != KeyType.RSA_HSM) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NonRSAKey")); - Object[] msgArgs = {retrievedKey.getKeyType().toString()}; - throw new SQLServerException(null, form.format(msgArgs), null, 0, false); + /** + * Encrypts the text using specified Azure Key Vault key. + * + * @param masterKeyPath - Azure Key Vault key url. + * @param encryptionAlgorithm - Encrypted Column Encryption Key. + * @param encryptedColumnEncryptionKey - Encrypted Column Encryption Key. + * @return Returns the decrypted plaintext Column Encryption Key or throws an exception if there are any errors. + * @throws SQLServerException + */ + private byte[] AzureKeyVaultUnWrap(String masterKeyPath, KeyWrapAlgorithm encryptionAlgorithm, + byte[] encryptedColumnEncryptionKey) throws SQLServerException { + if (null == encryptedColumnEncryptionKey) { + throw new SQLServerException(SQLServerException.getErrString("R_EncryptedCEKNull"), null); + } + + if (0 == encryptedColumnEncryptionKey.length) { + throw new SQLServerException(SQLServerException.getErrString("R_EmptyEncryptedCEK"), null); + } + + UnwrapResult unwrappedKey = cryptoClient.unwrapKey(encryptionAlgorithm, encryptedColumnEncryptionKey); + + return unwrappedKey.getKey(); } - if (null == cryptoClient) { - cryptoClient = new CryptographyClientBuilder() - .credential(credential) - .keyIdentifier(retrievedKey.getId()) - .buildClient(); + /** + * Generates signature based on RSA PKCS#v1.5 scheme using a specified Azure Key Vault Key URL. + * + * @param dataToSign - Text to sign. + * @param masterKeyPath - Azure Key Vault key url. + * @return Signature + * @throws SQLServerException + */ + private byte[] AzureKeyVaultSignHashedData(byte[] dataToSign, String masterKeyPath) throws SQLServerException { + assert ((null != dataToSign) && (0 != dataToSign.length)); + + SignResult signedData = cryptoClient.sign(SignatureAlgorithm.RS256, dataToSign); + return signedData.getSignature(); } - return retrievedKey.getKey().getN().length; - } + /** + * Verifies the given RSA PKCSv1.5 signature. + * + * @param dataToVerify + * @param signature + * @param masterKeyPath - Azure Key Vault key url. + * @return true if signature is valid, false if it is not valid + * @throws SQLServerException + */ + private boolean AzureKeyVaultVerifySignature(byte[] dataToVerify, byte[] signature, String masterKeyPath) + throws SQLServerException { + assert ((null != dataToVerify) && (0 != dataToVerify.length)); + assert ((null != signature) && (0 != signature.length)); - @Override - public boolean verifyColumnMasterKeyMetadata(String masterKeyPath, boolean allowEnclaveComputations, - byte[] signature) throws SQLServerException { - if (!allowEnclaveComputations) - return false; + VerifyResult valid = cryptoClient.verify(SignatureAlgorithm.RS256, dataToVerify, signature); - KeyStoreProviderCommon.validateNonEmptyMasterKeyPath(masterKeyPath); + return valid.isValid(); + } - try { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - md.update(name.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE)); - md.update(masterKeyPath.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE)); - // value of allowEnclaveComputations is always true here - md.update("true".getBytes(java.nio.charset.StandardCharsets.UTF_16LE)); + /** + * Returns the public Key size in bytes. + * + * @param masterKeyPath - Azure Key Vault Key path + * @return Key size in bytes + * @throws SQLServerException when an error occurs + */ + private int getAKVKeySize(String masterKeyPath) throws SQLServerException { + String[] keyTokens = masterKeyPath.split("/"); + String keyName = keyTokens[keyTokens.length - 2]; + KeyVaultKey retrievedKey = keyClient.getKey(keyName); + + if (null == retrievedKey) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AKVKeyNotFound")); + Object[] msgArgs = {keyTokens[keyTokens.length - 1]}; + throw new SQLServerException(null, form.format(msgArgs), null, 0, false); + } - byte[] dataToVerify = md.digest(); - if (null == dataToVerify) { - throw new SQLServerException(SQLServerException.getErrString("R_HashNull"), null); - } + if (retrievedKey.getKeyType() != KeyType.RSA && retrievedKey.getKeyType() != KeyType.RSA_HSM) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NonRSAKey")); + Object[] msgArgs = {retrievedKey.getKeyType().toString()}; + throw new SQLServerException(null, form.format(msgArgs), null, 0, false); + } + + if (null == cryptoClient) { + cryptoClient = new CryptographyClientBuilder().credential(credential) + .keyIdentifier(retrievedKey.getId()).buildClient(); + } - // Sign the hash - byte[] signedHash = AzureKeyVaultSignHashedData(dataToVerify, masterKeyPath); - if (null == signedHash) { - throw new SQLServerException(SQLServerException.getErrString("R_SignedHashLengthError"), null); + return retrievedKey.getKey().getN().length; + } + + @Override public boolean verifyColumnMasterKeyMetadata(String masterKeyPath, boolean allowEnclaveComputations, + byte[] signature) throws SQLServerException { + if (!allowEnclaveComputations) { + return false; } - // Validate the signature - return AzureKeyVaultVerifySignature(dataToVerify, signature, masterKeyPath); - } catch (NoSuchAlgorithmException e) { - throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), e); + KeyStoreProviderCommon.validateNonEmptyMasterKeyPath(masterKeyPath); + + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(name.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE)); + md.update(masterKeyPath.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE)); + // value of allowEnclaveComputations is always true here + md.update("true".getBytes(java.nio.charset.StandardCharsets.UTF_16LE)); + + byte[] dataToVerify = md.digest(); + if (null == dataToVerify) { + throw new SQLServerException(SQLServerException.getErrString("R_HashNull"), null); + } + + // Sign the hash + byte[] signedHash = AzureKeyVaultSignHashedData(dataToVerify, masterKeyPath); + if (null == signedHash) { + throw new SQLServerException(SQLServerException.getErrString("R_SignedHashLengthError"), + null); + } + + // Validate the signature + return AzureKeyVaultVerifySignature(dataToVerify, signature, masterKeyPath); + } catch (NoSuchAlgorithmException e) { + throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), e); + } } - } - - private static List getTrustedEndpoints() { - Properties mssqlJdbcProperties = getMssqlJdbcProperties(); - List trustedEndpoints = new ArrayList(); - boolean append = true; - if (null != mssqlJdbcProperties) { - String endpoints = mssqlJdbcProperties.getProperty(AKV_TRUSTED_ENDPOINTS_KEYWORD); - if (null != endpoints && !endpoints.trim().isEmpty()) { - endpoints = endpoints.trim(); - // Append if the list starts with a semicolon. - if (';' != endpoints.charAt(0)) { - append = false; - } else { - endpoints = endpoints.substring(1); + + private static List getTrustedEndpoints() { + Properties mssqlJdbcProperties = getMssqlJdbcProperties(); + List trustedEndpoints = new ArrayList(); + boolean append = true; + if (null != mssqlJdbcProperties) { + String endpoints = mssqlJdbcProperties.getProperty(AKV_TRUSTED_ENDPOINTS_KEYWORD); + if (null != endpoints && !endpoints.trim().isEmpty()) { + endpoints = endpoints.trim(); + // Append if the list starts with a semicolon. + if (';' != endpoints.charAt(0)) { + append = false; + } else { + endpoints = endpoints.substring(1); + } + String[] entries = endpoints.split(";"); + for (String entry : entries) { + if (null != entry && !entry.trim().isEmpty()) { + trustedEndpoints.add(entry.trim()); + } + } + } } - String[] entries = endpoints.split(";"); - for (String entry : entries) { - if (null != entry && !entry.trim().isEmpty()) { - trustedEndpoints.add(entry.trim()); - } + /* + * List of Azure trusted endpoints + * https://docs.microsoft.com/en-us/azure/key-vault/key-vault-secure-your-key-vault + */ + if (append) { + trustedEndpoints.add("vault.azure.net"); + trustedEndpoints.add("vault.azure.cn"); + trustedEndpoints.add("vault.usgovcloudapi.net"); + trustedEndpoints.add("vault.microsoftazure.de"); } - } + return trustedEndpoints; } - /* - * List of Azure trusted endpoints - * https://docs.microsoft.com/en-us/azure/key-vault/key-vault-secure-your-key-vault + + /** + * Attempt to read MSSQL_JDBC_PROPERTIES. + * + * @return corresponding Properties object or null if failed to read the file. */ - if (append) { - trustedEndpoints.add("vault.azure.net"); - trustedEndpoints.add("vault.azure.cn"); - trustedEndpoints.add("vault.usgovcloudapi.net"); - trustedEndpoints.add("vault.microsoftazure.de"); - } - return trustedEndpoints; - } - - /** - * Attempt to read MSSQL_JDBC_PROPERTIES. - * - * @return corresponding Properties object or null if failed to read the file. - */ - private static Properties getMssqlJdbcProperties() { - Properties props = null; - try (FileInputStream in = new FileInputStream(MSSQL_JDBC_PROPERTIES)) { - props = new Properties(); - props.load(in); - } catch (IOException e) { - if (akvLogger.isLoggable(Level.FINER)) { - akvLogger.finer("Unable to load the mssql-jdbc.properties file: " + e); - } + private static Properties getMssqlJdbcProperties() { + Properties props = null; + try (FileInputStream in = new FileInputStream(MSSQL_JDBC_PROPERTIES)) { + props = new Properties(); + props.load(in); + } catch (IOException e) { + if (akvLogger.isLoggable(Level.FINER)) { + akvLogger.finer("Unable to load the mssql-jdbc.properties file: " + e); + } + } + return (null != props && !props.isEmpty()) ? props : null; } - return (null != props && !props.isEmpty()) ? props : null; - } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 438ef6a30..eb4d51e9b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -1362,7 +1362,7 @@ private void registerKeyStoreProviderOnConnection(String keyStoreAuth, String ke } break; case KeyVaultClientSecret: - registerKeyVaultProvider(keyStorePrincipalId, keyStoreSecret, keyVaultProviderTenantId); + registerKeyVaultProvider(keyStorePrincipalId, keyStoreSecret); break; case KeyVaultManagedIdentity: SQLServerColumnEncryptionAzureKeyVaultProvider provider; @@ -1382,7 +1382,7 @@ private void registerKeyStoreProviderOnConnection(String keyStoreAuth, String ke } } - private void registerKeyVaultProvider(String clientId, String clientKey, String tenantId) throws SQLServerException { + private void registerKeyVaultProvider(String clientId, String clientKey) throws SQLServerException { // need a secret to use the secret method if (null == keyStoreSecret) { throw new SQLServerException(SQLServerException.getErrString("R_keyStoreSecretNotSet"), null); @@ -1640,8 +1640,7 @@ Connection connectInternal(Properties propsIn, registerKeyVaultProvider( keyVaultColumnEncryptionProviderClientId, - keyVaultColumnEncryptionProviderClientKey, - keyVaultProviderTenantId); + keyVaultColumnEncryptionProviderClientKey); } } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java index 278836486..0c85e7ea5 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java @@ -308,7 +308,7 @@ public static void testVerifyCMKNoEnclave() { try { SQLServerColumnEncryptionAzureKeyVaultProvider aksp = new SQLServerColumnEncryptionAzureKeyVaultProvider( - "","", ""); + "",""); } catch (SQLServerException e) { assertEquals(e.getMessage(), "Client ID cannot be null."); } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthCommon.java b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthCommon.java index cca7fc12d..3880f9018 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthCommon.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthCommon.java @@ -19,6 +19,7 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.logging.LogManager; import org.junit.jupiter.api.BeforeAll; @@ -159,7 +160,7 @@ static void getFedauthInfo() { final IAuthenticationResult authenticationResult = future.get(); - secondsBeforeExpiration = authenticationResult.expiresOnDate().getTime(); + secondsBeforeExpiration = TimeUnit.MILLISECONDS.toSeconds(authenticationResult.expiresOnDate().getTime()); accessToken = authenticationResult.accessToken(); } catch (Exception e) { fail(e.getMessage()); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthWithAE.java b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthWithAE.java index 8c71fa847..08942715b 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthWithAE.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthWithAE.java @@ -297,7 +297,7 @@ private SQLServerColumnEncryptionKeyStoreProvider setupKeyStoreProvider_JKS() th private SQLServerColumnEncryptionKeyStoreProvider setupKeyStoreProvider_AKVNew() throws SQLServerException { SQLServerConnection.unregisterColumnEncryptionKeyStoreProviders(); return registerAKVProvider( - new SQLServerColumnEncryptionAzureKeyVaultProvider(applicationClientID, applicationKey, tenantID)); + new SQLServerColumnEncryptionAzureKeyVaultProvider(applicationClientID, applicationKey)); } // private SQLServerColumnEncryptionKeyStoreProvider setupKeyStoreProvider_AKVOld() throws SQLServerException { diff --git a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java index fe3e16f47..56bd627f0 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java @@ -183,7 +183,7 @@ public static void setup() throws Exception { props.setProperty(Constants.AKV_TRUSTED_ENDPOINTS_KEYWORD, ";vault.azure.net"); props.store(os, ""); } - akvProvider = new SQLServerColumnEncryptionAzureKeyVaultProvider(applicationClientID, applicationKey, tenantID); + akvProvider = new SQLServerColumnEncryptionAzureKeyVaultProvider(applicationClientID, applicationKey); map.put(Constants.AZURE_KEY_VAULT_NAME, akvProvider); } finally { if (null != file) {