com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionJavaKeyStoreProvider Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mssql-jdbc Show documentation
Show all versions of mssql-jdbc Show documentation
Microsoft JDBC Driver for SQL Server.
/*
* Microsoft JDBC Driver for SQL Server Copyright(c) Microsoft Corporation All rights reserved. This program is made
* available under the terms of the MIT License. See the LICENSE file in the project root for more information.
*/
package com.microsoft.sqlserver.jdbc;
import static java.nio.charset.StandardCharsets.UTF_16LE;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.MessageFormat;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
/**
*
* Provides the implementation of the key store provider for Java Key Store. This class enables using certificates
* stored in the Java keystore as column master keys.
*
*/
public class SQLServerColumnEncryptionJavaKeyStoreProvider extends SQLServerColumnEncryptionKeyStoreProvider {
String name = "MSSQL_JAVA_KEYSTORE";
String keyStorePath = null;
char[] keyStorePwd = null;
static final private java.util.logging.Logger javaKeyStoreLogger = java.util.logging.Logger
.getLogger("com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionJavaKeyStoreProvider");
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
/**
* Constructs a SQLServerColumnEncryptionJavaKeyStoreProvider for the Java Key Store.
*
* @param keyStoreLocation
* specifies the location of the keystore
* @param keyStoreSecret
* specifies the secret used for keystore
* @throws SQLServerException
* when an error occurs
*/
public SQLServerColumnEncryptionJavaKeyStoreProvider(String keyStoreLocation,
char[] keyStoreSecret) throws SQLServerException {
javaKeyStoreLogger.entering(SQLServerColumnEncryptionJavaKeyStoreProvider.class.getName(),
"SQLServerColumnEncryptionJavaKeyStoreProvider");
if ((null == keyStoreLocation) || (0 == keyStoreLocation.length())) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidConnectionSetting"));
Object[] msgArgs = {"keyStoreLocation", keyStoreLocation};
throw new SQLServerException(form.format(msgArgs), null);
}
this.keyStorePath = keyStoreLocation;
if (javaKeyStoreLogger.isLoggable(java.util.logging.Level.FINE)) {
javaKeyStoreLogger.fine("Path of key store provider is set.");
}
// Password can be null or empty, PKCS12 type allows that.
if (null == keyStoreSecret) {
keyStoreSecret = "".toCharArray();
}
this.keyStorePwd = new char[keyStoreSecret.length];
System.arraycopy(keyStoreSecret, 0, this.keyStorePwd, 0, keyStoreSecret.length);
if (javaKeyStoreLogger.isLoggable(java.util.logging.Level.FINE)) {
javaKeyStoreLogger.fine("Password for key store provider is set.");
}
javaKeyStoreLogger.exiting(SQLServerColumnEncryptionJavaKeyStoreProvider.class.getName(),
"SQLServerColumnEncryptionJavaKeyStoreProvider");
}
@Override
public byte[] decryptColumnEncryptionKey(String masterKeyPath, String encryptionAlgorithm,
byte[] encryptedColumnEncryptionKey) throws SQLServerException {
javaKeyStoreLogger.entering(SQLServerColumnEncryptionJavaKeyStoreProvider.class.getName(),
"decryptColumnEncryptionKey", "Decrypting Column Encryption Key.");
KeyStoreProviderCommon.validateNonEmptyMasterKeyPath(masterKeyPath);
CertificateDetails certificateDetails = getCertificateDetails(masterKeyPath);
byte[] plainCEK = KeyStoreProviderCommon.decryptColumnEncryptionKey(masterKeyPath, encryptionAlgorithm,
encryptedColumnEncryptionKey, certificateDetails);
javaKeyStoreLogger.exiting(SQLServerColumnEncryptionJavaKeyStoreProvider.class.getName(),
"decryptColumnEncryptionKey", "Finished decrypting Column Encryption Key.");
return plainCEK;
}
private CertificateDetails getCertificateDetails(String masterKeyPath) throws SQLServerException {
FileInputStream fis = null;
KeyStore keyStore = null;
CertificateDetails certificateDetails = null;
try {
if (null == masterKeyPath || 0 == masterKeyPath.length()) {
throw new SQLServerException(null, SQLServerException.getErrString("R_InvalidMasterKeyDetails"), null,
0, false);
}
try {
// Try to load JKS first, if fails try PKCS12
keyStore = KeyStore.getInstance("JKS");
fis = new FileInputStream(keyStorePath);
keyStore.load(fis, keyStorePwd);
} catch (IOException e) {
if (null != fis)
fis.close();
// Loading as JKS failed, try to load as PKCS12
keyStore = KeyStore.getInstance("PKCS12");
fis = new FileInputStream(keyStorePath);
keyStore.load(fis, keyStorePwd);
}
certificateDetails = getCertificateDetailsByAlias(keyStore, masterKeyPath);
} catch (FileNotFoundException fileNotFound) {
throw new SQLServerException(this, SQLServerException.getErrString("R_KeyStoreNotFound"), null, 0, false);
} catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidKeyStoreFile"));
Object[] msgArgs = {keyStorePath};
throw new SQLServerException(form.format(msgArgs), e);
} finally {
try {
if (null != fis)
fis.close();
}
// Ignore the exception as we are cleaning up.
catch (IOException e) {}
}
return certificateDetails;
}
private CertificateDetails getCertificateDetailsByAlias(KeyStore keyStore, String alias) throws SQLServerException {
try {
X509Certificate publicCertificate = (X509Certificate) keyStore.getCertificate(alias);
Key keyPrivate = keyStore.getKey(alias, keyStorePwd);
if (null == publicCertificate) {
// Certificate not found. Throw an exception.
MessageFormat form = new MessageFormat(
SQLServerException.getErrString("R_CertificateNotFoundForAlias"));
Object[] msgArgs = {alias, "MSSQL_JAVA_KEYSTORE"};
throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
}
// found certificate but corresponding private key not found, throw exception
if (null == keyPrivate) {
throw new UnrecoverableKeyException();
}
return new CertificateDetails(publicCertificate, keyPrivate);
} catch (UnrecoverableKeyException unrecoverableKeyException) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_UnrecoverableKeyAE"));
Object[] msgArgs = {alias};
throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
} catch (NoSuchAlgorithmException | KeyStoreException e) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_CertificateError"));
Object[] msgArgs = {alias, name};
throw new SQLServerException(form.format(msgArgs), e);
}
}
@Override
public byte[] encryptColumnEncryptionKey(String masterKeyPath, String encryptionAlgorithm,
byte[] plainTextColumnEncryptionKey) throws SQLServerException {
javaKeyStoreLogger.entering(SQLServerColumnEncryptionJavaKeyStoreProvider.class.getName(),
"encryptColumnEncryptionKey", "Encrypting Column Encryption Key.");
byte[] version = KeyStoreProviderCommon.version;
KeyStoreProviderCommon.validateNonEmptyMasterKeyPath(masterKeyPath);
if (null == plainTextColumnEncryptionKey) {
throw new SQLServerException(null, SQLServerException.getErrString("R_NullColumnEncryptionKey"), null, 0,
false);
} else if (0 == plainTextColumnEncryptionKey.length) {
throw new SQLServerException(null, SQLServerException.getErrString("R_EmptyColumnEncryptionKey"), null, 0,
false);
}
KeyStoreProviderCommon.validateEncryptionAlgorithm(encryptionAlgorithm, true);
CertificateDetails certificateDetails = getCertificateDetails(masterKeyPath);
byte[] cipherText = encryptRSAOAEP(plainTextColumnEncryptionKey, certificateDetails);
byte[] cipherTextLength = getLittleEndianBytesFromShort((short) cipherText.length);
byte[] masterKeyPathBytes = masterKeyPath.toLowerCase().getBytes(UTF_16LE);
byte[] keyPathLength = getLittleEndianBytesFromShort((short) masterKeyPathBytes.length);
byte[] dataToSign = new byte[version.length + keyPathLength.length + cipherTextLength.length
+ masterKeyPathBytes.length + cipherText.length];
int destinationPosition = version.length;
System.arraycopy(version, 0, dataToSign, 0, version.length);
System.arraycopy(keyPathLength, 0, dataToSign, destinationPosition, keyPathLength.length);
destinationPosition += keyPathLength.length;
System.arraycopy(cipherTextLength, 0, dataToSign, destinationPosition, cipherTextLength.length);
destinationPosition += cipherTextLength.length;
System.arraycopy(masterKeyPathBytes, 0, dataToSign, destinationPosition, masterKeyPathBytes.length);
destinationPosition += masterKeyPathBytes.length;
System.arraycopy(cipherText, 0, dataToSign, destinationPosition, cipherText.length);
byte[] signedHash = rsaSignHashedData(dataToSign, certificateDetails);
int encryptedColumnEncryptionKeyLength = version.length + cipherTextLength.length + keyPathLength.length
+ cipherText.length + masterKeyPathBytes.length + signedHash.length;
byte[] encryptedColumnEncryptionKey = new byte[encryptedColumnEncryptionKeyLength];
int currentIndex = 0;
System.arraycopy(version, 0, encryptedColumnEncryptionKey, currentIndex, version.length);
currentIndex += version.length;
System.arraycopy(keyPathLength, 0, encryptedColumnEncryptionKey, currentIndex, keyPathLength.length);
currentIndex += keyPathLength.length;
System.arraycopy(cipherTextLength, 0, encryptedColumnEncryptionKey, currentIndex, cipherTextLength.length);
currentIndex += cipherTextLength.length;
System.arraycopy(masterKeyPathBytes, 0, encryptedColumnEncryptionKey, currentIndex, masterKeyPathBytes.length);
currentIndex += masterKeyPathBytes.length;
System.arraycopy(cipherText, 0, encryptedColumnEncryptionKey, currentIndex, cipherText.length);
currentIndex += cipherText.length;
System.arraycopy(signedHash, 0, encryptedColumnEncryptionKey, currentIndex, signedHash.length);
javaKeyStoreLogger.exiting(SQLServerColumnEncryptionJavaKeyStoreProvider.class.getName(),
"encryptColumnEncryptionKey", "Finished encrypting Column Encryption Key.");
return encryptedColumnEncryptionKey;
}
/**
* Encrypt plainText with the certificate provided.
*
* @param plainText
* plain CEK to be encrypted
* @param certificateDetails
* @return encrypted CEK
* @throws SQLServerException
*/
private byte[] encryptRSAOAEP(byte[] plainText, CertificateDetails certificateDetails) throws SQLServerException {
byte[] cipherText = null;
try {
Cipher rsa = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
rsa.init(Cipher.ENCRYPT_MODE, certificateDetails.certificate.getPublicKey());
rsa.update(plainText);
cipherText = rsa.doFinal();
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException
| BadPaddingException e) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_EncryptionFailed"));
Object[] msgArgs = {e.getMessage()};
throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
}
return cipherText;
}
private byte[] rsaSignHashedData(byte[] dataToSign,
CertificateDetails certificateDetails) throws SQLServerException {
Signature signature;
byte[] signedHash = null;
try {
signature = Signature.getInstance("SHA256withRSA");
signature.initSign((PrivateKey) certificateDetails.privateKey);
signature.update(dataToSign);
signedHash = signature.sign();
} catch (InvalidKeyException | NoSuchAlgorithmException | SignatureException e) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_EncryptionFailed"));
Object[] msgArgs = {e.getMessage()};
throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
}
return signedHash;
}
private byte[] getLittleEndianBytesFromShort(short value) {
ByteBuffer byteBuffer = ByteBuffer.allocate(2);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
byte[] byteValue = byteBuffer.putShort(value).array();
return byteValue;
}
}