From 3b099d93c16b25f64156075f5c9910a6e6825f14 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Thu, 14 Nov 2019 15:26:07 -0800 Subject: [PATCH 01/25] Add AAS provider --- .../jdbc/ISQLServerEnclaveProvider.java | 160 +++++- .../jdbc/SQLServerAASEnclaveProvider.java | 471 ++++++++++++++++++ .../sqlserver/jdbc/SQLServerConnection.java | 8 +- .../sqlserver/jdbc/SQLServerDriver.java | 8 +- .../jdbc/SQLServerPreparedStatement.java | 2 +- .../jdbc/SQLServerVSMEnclaveProvider.java | 144 +----- 6 files changed, 649 insertions(+), 144 deletions(-) create mode 100644 src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java index 51f75ec6a..c1890afbf 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java @@ -5,10 +5,30 @@ package com.microsoft.sqlserver.jdbc; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.MessageDigest; import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.RSAPublicKeySpec; import java.util.ArrayList; +import java.util.Arrays; import java.util.concurrent.atomic.AtomicInteger; +import javax.crypto.KeyAgreement; + + /** * * Provides an interface to create an Enclave Session @@ -19,6 +39,7 @@ public interface ISQLServerEnclaveProvider { /** * Returns the attestation parameters + * * @param createNewParameters * indicates whether to create new parameters * @param url @@ -41,8 +62,7 @@ public interface ISQLServerEnclaveProvider { * params * @param parameterNames * parameterNames - * @return - * list of enclave requested CEKs + * @return list of enclave requested CEKs * @throws SQLServerException * when an error occurs. */ @@ -57,8 +77,8 @@ ArrayList createEnclaveSession(SQLServerConnection connection, String us /** * Returns the enclave session - * @return - * the enclave session + * + * @return the enclave session */ EnclaveSession getEnclaveSession(); } @@ -66,10 +86,142 @@ ArrayList createEnclaveSession(SQLServerConnection connection, String us abstract class BaseAttestationRequest { protected PrivateKey privateKey; + // Static byte[] for VSM ECDH + protected static byte ECDH_MAGIC[] = {0x45, 0x43, 0x4b, 0x33, 0x30, 0x00, 0x00, 0x00}; + // VSM doesn't have a challenge + protected byte enclaveChallenge[]; + protected static int ENCLAVE_LENGTH = 104; + protected byte[] x; + protected byte[] y; byte[] getBytes() { return null; }; + + byte[] createSessionSecret(byte[] serverResponse) throws GeneralSecurityException, SQLServerException { + if (serverResponse.length != ENCLAVE_LENGTH) { + SQLServerException.makeFromDriverError(null, this, + SQLServerResource.getResource("R_MalformedECDHPublicKey"), "0", false); + } + ByteBuffer sr = ByteBuffer.wrap(serverResponse); + byte[] magic = new byte[8]; + sr.get(magic); + if (!Arrays.equals(magic, ECDH_MAGIC)) { + SQLServerException.makeFromDriverError(null, this, SQLServerResource.getResource("R_MalformedECDHHeader"), + "0", false); + } + byte[] x = new byte[48]; + byte[] y = new byte[48]; + sr.get(x); + sr.get(y); + /* + * Server returns X and Y coordinates, create a key using the point of the server and our key parameters. + * Public/Private key parameters are the same. + */ + ECPublicKeySpec keySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(1, x), new BigInteger(1, y)), + ((ECPrivateKey) privateKey).getParams()); + KeyAgreement ka = KeyAgreement.getInstance("ECDH"); + ka.init(privateKey); + // Generate a PublicKey from the above key specifications and do an agreement with our PrivateKey + ka.doPhase(KeyFactory.getInstance("EC").generatePublic(keySpec), true); + // Generate a Secret from the agreement and hash with SHA-256 to create Session Secret + return MessageDigest.getInstance("SHA-256").digest(ka.generateSecret()); + } + + void initBcryptECDH() throws SQLServerException { + /* + * Create our BCRYPT_ECCKEY_BLOB + */ + KeyPairGenerator kpg = null; + try { + kpg = KeyPairGenerator.getInstance("EC"); + kpg.initialize(new ECGenParameterSpec("secp384r1")); + } catch (GeneralSecurityException e) { + SQLServerException.makeFromDriverError(null, kpg, e.getLocalizedMessage(), "0", false); + } + KeyPair kp = kpg.generateKeyPair(); + ECPublicKey publicKey = (ECPublicKey) kp.getPublic(); + privateKey = kp.getPrivate(); + ECPoint w = publicKey.getW(); + x = w.getAffineX().toByteArray(); + y = w.getAffineY().toByteArray(); + + /* + * For some reason toByteArray doesn't have an Signum option like the constructor. Manually remove leading 00 + * byte if it exists. + */ + if (x[0] == 0 && x.length != 48) { + x = Arrays.copyOfRange(x, 1, x.length); + } + if (y[0] == 0 && y.length != 48) { + y = Arrays.copyOfRange(y, 1, y.length); + } + } +} + +abstract class BaseAttestationResponse { + protected int totalSize; + protected int identitySize; + protected int attestationTokenSize; + protected int enclaveType; + + protected byte[] enclavePK; + protected int sessionInfoSize; + protected byte[] sessionID = new byte[8]; + protected int DHPKsize; + protected int DHPKSsize; + protected byte[] DHpublicKey; + protected byte[] publicKeySig; + + void validateDHPublicKey() throws SQLServerException, GeneralSecurityException { + /*- + * Java doesn't directly support PKCS1 padding for RSA keys. Parse the key bytes and create a RSAPublicKeySpec + * with the exponent and modulus. + * + * Static string "RSA1" - 4B (Unused) + * Bit count - 4B (Unused) + * Public Exponent Length - 4B + * Public Modulus Length - 4B + * Prime 1 - 4B (Unused) + * Prime 2 - 4B (Unused) + * Exponent - publicExponentLength bytes + * Modulus - publicModulusLength bytes + */ + ByteBuffer enclavePKBuffer = ByteBuffer.wrap(enclavePK).order(ByteOrder.LITTLE_ENDIAN); + byte[] rsa1 = new byte[4]; + enclavePKBuffer.get(rsa1); + int bitCount = enclavePKBuffer.getInt(); + int publicExponentLength = enclavePKBuffer.getInt(); + int publicModulusLength = enclavePKBuffer.getInt(); + int prime1 = enclavePKBuffer.getInt(); + int prime2 = enclavePKBuffer.getInt(); + byte[] exponent = new byte[publicExponentLength]; + enclavePKBuffer.get(exponent); + byte[] modulus = new byte[publicModulusLength]; + enclavePKBuffer.get(modulus); + if (enclavePKBuffer.remaining() != 0) { + SQLServerException.makeFromDriverError(null, this, SQLServerResource.getResource("R_EnclavePKLengthError"), + "0", false); + } + RSAPublicKeySpec spec = new RSAPublicKeySpec(new BigInteger(1, modulus), new BigInteger(1, exponent)); + KeyFactory factory = KeyFactory.getInstance("RSA"); + PublicKey pub = factory.generatePublic(spec); + Signature sig = Signature.getInstance("SHA256withRSA"); + sig.initVerify(pub); + sig.update(DHpublicKey); + if (!sig.verify(publicKeySig)) { + SQLServerException.makeFromDriverError(null, this, SQLServerResource.getResource("R_InvalidDHKeySignature"), + "0", false); + } + } + + byte[] getDHpublicKey() { + return DHpublicKey; + } + + byte[] getSessionID() { + return sessionID; + } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java new file mode 100644 index 000000000..0f1509349 --- /dev/null +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java @@ -0,0 +1,471 @@ +/* + * 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.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.net.URL; +import java.net.URLConnection; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.MessageDigest; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Signature; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.KeyAgreement; + +import org.apache.commons.codec.binary.Base64; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + + +/** + * + * Provides the implementation of the VSM Enclave Provider. The enclave provider encapsulates the client-side + * implementation details of the enclave attestation protocol. + * + */ +public class SQLServerAASEnclaveProvider implements ISQLServerEnclaveProvider { + + private AASAttestationParameters aasParams = null; + private AASAttestationResponse hgsResponse = null; + private String attestationURL = null; + private EnclaveSession enclaveSession = null; + + @Override + public void getAttestationParameters(boolean createNewParameters, String url) throws SQLServerException { + if (null == aasParams || createNewParameters) { + attestationURL = url; + try { + aasParams = new AASAttestationParameters(attestationURL); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + @Override + public ArrayList createEnclaveSession(SQLServerConnection connection, String userSql, + String preparedTypeDefinitions, Parameter[] params, + ArrayList parameterNames) throws SQLServerException { + ArrayList b = describeParameterEncryption(connection, userSql, preparedTypeDefinitions, params, + parameterNames); + if (null != hgsResponse && !connection.enclaveEstablished()) { + try { + enclaveSession = new EnclaveSession(hgsResponse.getSessionID(), + aasParams.createSessionSecret(hgsResponse.getDHpublicKey())); + } catch (GeneralSecurityException e) { + SQLServerException.makeFromDriverError(connection, this, e.getLocalizedMessage(), "0", false); + } + } + return b; + } + + @Override + public void invalidateEnclaveSession() { + enclaveSession = null; + aasParams = null; + attestationURL = null; + } + + @Override + public EnclaveSession getEnclaveSession() { + return enclaveSession; + } + + @Override + public byte[] getEnclavePackage(String userSQL, ArrayList enclaveCEKs) throws SQLServerException { + if (null != enclaveSession) { + try { + ByteArrayOutputStream enclavePackage = new ByteArrayOutputStream(); + enclavePackage.writeBytes(enclaveSession.getSessionID()); + ByteArrayOutputStream keys = new ByteArrayOutputStream(); + byte[] randomGUID = new byte[16]; + SecureRandom.getInstanceStrong().nextBytes(randomGUID); + keys.writeBytes(randomGUID); + keys.writeBytes(ByteBuffer.allocate(8).putLong(enclaveSession.getCounter()).array()); + keys.writeBytes(MessageDigest.getInstance("SHA-256").digest((userSQL).getBytes(UTF_16LE))); + for (byte[] b : enclaveCEKs) { + keys.writeBytes(b); + } + enclaveCEKs.clear(); + SQLServerAeadAes256CbcHmac256EncryptionKey encryptedKey = new SQLServerAeadAes256CbcHmac256EncryptionKey( + enclaveSession.getSessionSecret(), SQLServerAeadAes256CbcHmac256Algorithm.algorithmName); + SQLServerAeadAes256CbcHmac256Algorithm algo = new SQLServerAeadAes256CbcHmac256Algorithm(encryptedKey, + SQLServerEncryptionType.Randomized, (byte) 0x1); + enclavePackage.writeBytes(algo.encryptData(keys.toByteArray())); + return enclavePackage.toByteArray(); + } catch (GeneralSecurityException | SQLServerException e) { + SQLServerException.makeFromDriverError(null, this, e.getLocalizedMessage(), "0", false); + } + } + return null; + } + + private AASAttestationResponse validateAttestationResponse(AASAttestationResponse ar) throws SQLServerException { + try { + ar.validateToken(attestationURL); + ar.validateDHPublicKey(aasParams.getNonce()); + } catch (GeneralSecurityException e) { + SQLServerException.makeFromDriverError(null, this, e.getLocalizedMessage(), "0", false); + } + return ar; + } + + private ArrayList describeParameterEncryption(SQLServerConnection connection, String userSql, + String preparedTypeDefinitions, Parameter[] params, + ArrayList parameterNames) throws SQLServerException { + ArrayList enclaveRequestedCEKs = new ArrayList<>(); + ResultSet rs = null; + try (PreparedStatement stmt = connection.prepareStatement("EXEC sp_describe_parameter_encryption ?,?,?")) { + ((SQLServerPreparedStatement) stmt).isInternalEncryptionQuery = true; + stmt.setNString(1, userSql); + if (preparedTypeDefinitions != null && preparedTypeDefinitions.length() != 0) { + stmt.setNString(2, preparedTypeDefinitions); + } else { + stmt.setNString(2, ""); + } + stmt.setBytes(3, aasParams.getBytes()); + rs = ((SQLServerPreparedStatement) stmt).executeQueryInternal(); + + if (null == rs) { + // No results. Meaning no parameter. + // Should never happen. + return enclaveRequestedCEKs; + } + + Map cekList = new HashMap<>(); + CekTableEntry cekEntry = null; + boolean isRequestedByEnclave = false; + try { + while (rs.next()) { + int currentOrdinal = rs.getInt(DescribeParameterEncryptionResultSet1.KeyOrdinal.value()); + if (!cekList.containsKey(currentOrdinal)) { + cekEntry = new CekTableEntry(currentOrdinal); + cekList.put(cekEntry.ordinal, cekEntry); + } else { + cekEntry = cekList.get(currentOrdinal); + } + + String keyStoreName = rs.getString(DescribeParameterEncryptionResultSet1.ProviderName.value()); + String algo = rs.getString(DescribeParameterEncryptionResultSet1.KeyEncryptionAlgorithm.value()); + String keyPath = rs.getString(DescribeParameterEncryptionResultSet1.KeyPath.value()); + + int dbID = rs.getInt(DescribeParameterEncryptionResultSet1.DbId.value()); + byte[] mdVer = rs.getBytes(DescribeParameterEncryptionResultSet1.KeyMdVersion.value()); + int keyID = rs.getInt(DescribeParameterEncryptionResultSet1.KeyId.value()); + byte[] encryptedKey = rs.getBytes(DescribeParameterEncryptionResultSet1.EncryptedKey.value()); + + cekEntry.add(encryptedKey, dbID, keyID, + rs.getInt(DescribeParameterEncryptionResultSet1.KeyVersion.value()), mdVer, keyPath, + keyStoreName, algo); + + // servers supporting enclave computations should always return a boolean indicating whether the key + // is + // required by enclave or not. + if (ColumnEncryptionVersion.AE_v2.value() <= connection.getServerColumnEncryptionVersion() + .value()) { + isRequestedByEnclave = rs + .getBoolean(DescribeParameterEncryptionResultSet1.IsRequestedByEnclave.value()); + } + + if (isRequestedByEnclave) { + byte[] keySignature = rs + .getBytes(DescribeParameterEncryptionResultSet1.EnclaveCMKSignature.value()); + String serverName = connection.getTrustedServerNameAE(); + SQLServerSecurityUtility.verifyColumnMasterKeyMetadata(connection, keyStoreName, keyPath, + serverName, isRequestedByEnclave, keySignature); + + // DBID(4) + MDVER(8) + KEYID(2) + CEK(32) = 46 + ByteBuffer aev2CekEntry = ByteBuffer.allocate(46); + aev2CekEntry.order(ByteOrder.LITTLE_ENDIAN).putInt(dbID); + aev2CekEntry.put(mdVer); + aev2CekEntry.putShort((short) keyID); + aev2CekEntry.put(connection.getColumnEncryptionKeyStoreProvider(keyStoreName) + .decryptColumnEncryptionKey(keyPath, algo, encryptedKey)); + enclaveRequestedCEKs.add(aev2CekEntry.array()); + } + } + } catch (SQLException e) { + if (e instanceof SQLServerException) { + throw (SQLServerException) e; + } else { + throw new SQLServerException(SQLServerException.getErrString("R_UnableRetrieveParameterMetadata"), + null, 0, e); + } + } + + // Process the second resultset. + if (!stmt.getMoreResults()) { + throw new SQLServerException(this, SQLServerException.getErrString("R_UnexpectedDescribeParamFormat"), + null, 0, false); + } + + try { + rs = (SQLServerResultSet) stmt.getResultSet(); + while (rs.next() && null != params) { + String paramName = rs.getString(DescribeParameterEncryptionResultSet2.ParameterName.value()); + int paramIndex = parameterNames.indexOf(paramName); + int cekOrdinal = rs + .getInt(DescribeParameterEncryptionResultSet2.ColumnEncryptionKeyOrdinal.value()); + cekEntry = cekList.get(cekOrdinal); + + // cekEntry will be null if none of the parameters are encrypted. + if ((null != cekEntry) && (cekList.size() < cekOrdinal)) { + MessageFormat form = new MessageFormat( + SQLServerException.getErrString("R_InvalidEncryptionKeyOridnal")); + Object[] msgArgs = {cekOrdinal, cekEntry.getSize()}; + throw new SQLServerException(this, form.format(msgArgs), null, 0, false); + } + SQLServerEncryptionType encType = SQLServerEncryptionType + .of((byte) rs.getInt(DescribeParameterEncryptionResultSet2.ColumnEncrytionType.value())); + if (SQLServerEncryptionType.PlainText != encType) { + params[paramIndex].cryptoMeta = new CryptoMetadata(cekEntry, (short) cekOrdinal, + (byte) rs.getInt( + DescribeParameterEncryptionResultSet2.ColumnEncryptionAlgorithm.value()), + null, encType.value, (byte) rs.getInt( + DescribeParameterEncryptionResultSet2.NormalizationRuleVersion.value())); + // Decrypt the symmetric key.(This will also validate and throw if needed). + SQLServerSecurityUtility.decryptSymmetricKey(params[paramIndex].cryptoMeta, connection); + } else { + if (params[paramIndex].getForceEncryption()) { + MessageFormat form = new MessageFormat(SQLServerException + .getErrString("R_ForceEncryptionTrue_HonorAETrue_UnencryptedColumn")); + Object[] msgArgs = {userSql, paramIndex + 1}; + SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), "0", true); + } + } + } + } catch (SQLException e) { + if (e instanceof SQLServerException) { + throw (SQLServerException) e; + } else { + throw new SQLServerException(SQLServerException.getErrString("R_UnableRetrieveParameterMetadata"), + null, 0, e); + } + } + + // Process the third resultset. + if (connection.isAEv2() && stmt.getMoreResults()) { + rs = (SQLServerResultSet) stmt.getResultSet(); + while (rs.next()) { + hgsResponse = new AASAttestationResponse(rs.getBytes(1)); + // This validates and establishes the enclave session if valid + if (!connection.enclaveEstablished()) { + hgsResponse = validateAttestationResponse(hgsResponse); + } + } + } + + // Null check for rs is done already. + rs.close(); + } catch (SQLException e) { + if (e instanceof SQLServerException) { + throw (SQLServerException) e; + } else { + throw new SQLServerException(SQLServerException.getErrString("R_UnableRetrieveParameterMetadata"), null, + 0, e); + } + } + return enclaveRequestedCEKs; + } +} + + +class AASAttestationParameters extends BaseAttestationRequest { + + // Type 1 is AAS, sent as Little Endian 0x10000000 + private static byte ENCLAVE_TYPE[] = new byte[] {0x1, 0x0, 0x0, 0x0}; + // Nonce length is always 256 + private static byte NONCE_LENGTH[] = new byte[] {0x0, 0x1, 0x0, 0x0}; + private byte[] nonce = new byte[256]; + + AASAttestationParameters(String attestationUrl) throws SQLServerException, IOException { + byte[] attestationUrlBytes = (attestationUrl + '\0').getBytes(UTF_16LE); + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + os.writeBytes(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(attestationUrlBytes.length).array()); + os.writeBytes(attestationUrlBytes); + os.writeBytes(NONCE_LENGTH); + new SecureRandom().nextBytes(nonce); + os.writeBytes(nonce); + enclaveChallenge = os.toByteArray(); + + initBcryptECDH(); + } + + @Override + byte[] getBytes() { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + os.writeBytes(ENCLAVE_TYPE); + os.writeBytes(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(enclaveChallenge.length).array()); + os.writeBytes(enclaveChallenge); + os.writeBytes(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ENCLAVE_LENGTH).array()); + os.writeBytes(ECDH_MAGIC); + os.writeBytes(x); + os.writeBytes(y); + return os.toByteArray(); + } + + byte[] getNonce() { + return nonce; + } +} + + +@SuppressWarnings("unused") +class AASAttestationResponse extends BaseAttestationResponse { + + private byte[] attestationToken; + private static Map certificateCache = new HashMap<>(); + + AASAttestationResponse(byte[] b) throws SQLServerException { + /*- + * A model class representing the deserialization of the byte payload the client + * receives from SQL Server while setting up a session. + * Protocol format: + * 1. Total Size of the attestation blob as UINT + * 2. Size of Enclave RSA public key as UINT + * 3. Size of Attestation token as UINT + * 4. Enclave Type as UINT + * 5. Enclave RSA public key (raw key, of length #2) + * 6. Attestation token (of length #3) + * 7. Size of Session Id was UINT + * 8. Session id value + * 9. Size of enclave ECDH public key + * 10. Enclave ECDH public key (of length #9) + */ + ByteBuffer response = ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN); + this.totalSize = response.getInt(); + this.identitySize = response.getInt(); + this.attestationTokenSize = response.getInt(); + this.enclaveType = response.getInt(); // 1 for VBS, 2 for SGX + + enclavePK = new byte[identitySize]; + attestationToken = new byte[attestationTokenSize]; + + response.get(enclavePK, 0, identitySize); + response.get(attestationToken, 0, attestationTokenSize); + + this.sessionInfoSize = response.getInt(); + response.get(sessionID, 0, 8); + this.DHPKsize = response.getInt(); + this.DHPKSsize = response.getInt(); + + DHpublicKey = new byte[DHPKsize]; + publicKeySig = new byte[DHPKSsize]; + + response.get(DHpublicKey, 0, DHPKsize); + response.get(publicKeySig, 0, DHPKSsize); + + if (0 != response.remaining()) { + SQLServerException.makeFromDriverError(null, this, + SQLServerResource.getResource("R_EnclaveResponseLengthError"), "0", false); + } + } + + void validateToken(String attestationUrl) throws SQLServerException { + /* + * 3 parts of our JWT token: Header, Body, and Signature. Broken up via '.' + */ + String jwtToken = (new String(attestationToken)).trim(); + if (jwtToken.startsWith("\"") && jwtToken.endsWith("\"")) { + jwtToken = jwtToken.substring(1, jwtToken.length() - 1); + } + String[] splitString = jwtToken.split("\\."); + String Header = new String(Base64.decodeBase64(splitString[0])); + String Body = new String(Base64.decodeBase64(splitString[1])); + byte[] stmtSig = Base64.decodeBase64(splitString[2]); + + try { + JsonArray keys = certificateCache.get(attestationUrl); + if (null == keys) { + // Use the attestation URL to find where our keys are + String authorityUrl = new URL(attestationUrl).getAuthority(); + URL wellKnownUrl = new URL("https://" + authorityUrl + "/.well-known/openid-configuration"); + URLConnection con = wellKnownUrl.openConnection(); + String wellKnownUrlJson = new String(con.getInputStream().readAllBytes()); + JsonObject attestationJson = new JsonParser().parse(wellKnownUrlJson).getAsJsonObject(); + // Get our Keys + URL jwksUrl = new URL(attestationJson.get("jwks_uri").getAsString()); + URLConnection jwksCon = jwksUrl.openConnection(); + String jwksUrlJson = new String(jwksCon.getInputStream().readAllBytes()); + JsonObject jwksJson = new JsonParser().parse(jwksUrlJson).getAsJsonObject(); + keys = jwksJson.get("keys").getAsJsonArray(); + certificateCache.put(attestationUrl, keys); + } + // Find the specific keyID we need from our header + JsonObject headerJsonObject = new JsonParser().parse(Header).getAsJsonObject(); + String keyID = headerJsonObject.get("kid").getAsString(); + // Iterate through our list of keys and find the one with the same keyID + for (JsonElement key : keys) { + JsonObject keyObj = key.getAsJsonObject(); + String kId = keyObj.get("kid").getAsString(); + if (kId.equals(keyID)) { + JsonArray certsFromServer = keyObj.get("x5c").getAsJsonArray(); + /* + * To create the signature part you have to take the encoded header, the encoded payload, a secret, + * the algorithm specified in the header, and sign that. + */ + byte[] signatureBytes = (splitString[0] + "." + splitString[1]).getBytes(); + for (JsonElement jsonCert : certsFromServer) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) cf.generateCertificate( + new ByteArrayInputStream(java.util.Base64.getDecoder().decode(jsonCert.getAsString()))); + Signature sig = Signature.getInstance("SHA256withRSA"); + sig.initVerify(cert.getPublicKey()); + sig.update(signatureBytes); + if (sig.verify(stmtSig)) { + return; + } + } + } + } + SQLServerException.makeFromDriverError(null, this, SQLServerResource.getResource("TODO: ADD MESSAGE"), "0", + false); + } catch (IOException | GeneralSecurityException e) { + SQLServerException.makeFromDriverError(null, this, e.getLocalizedMessage(), "", false); + } + } + + void validateDHPublicKey(byte[] nonce) throws SQLServerException, GeneralSecurityException { + if (this.enclaveType == 2) { + for (int i = 0; i < enclavePK.length; i++) { + enclavePK[i] = (byte) (enclavePK[i] ^ nonce[i % nonce.length]); + } + } + validateDHPublicKey(); + } +} diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 04366fa27..4812701e5 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -1472,7 +1472,11 @@ Connection connectInternal(Properties propsIn, sPropValue = activeConnectionProperties.getProperty(sPropKey); if (null != sPropValue) { enclaveAttestationProtocol = sPropValue; - if (!AttestationProtocol.isValidAttestationProtocol(enclaveAttestationProtocol)) { + if (enclaveAttestationProtocol.equalsIgnoreCase("HGS")) { + this.enclaveProvider = new SQLServerVSMEnclaveProvider(); + } else if (enclaveAttestationProtocol.equalsIgnoreCase("AAS")) { + this.enclaveProvider = new SQLServerAASEnclaveProvider(); + } else { if (connectionlogger.isLoggable(Level.SEVERE)) { connectionlogger.severe(toString() + " " + SQLServerException.getErrString("R_enclaveInvalidAttestationProtocol")); @@ -6471,7 +6475,7 @@ boolean isAEv2() { return (aeVersion >= TDS.COLUMNENCRYPTION_VERSION2); } - ISQLServerEnclaveProvider enclaveProvider = new SQLServerVSMEnclaveProvider(); + ISQLServerEnclaveProvider enclaveProvider; ArrayList initEnclaveParameters(String userSql, String preparedTypeDefinitions, Parameter[] params, ArrayList parameterNames) throws SQLServerException { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java index 4e30fa3f9..d389455d9 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java @@ -114,8 +114,9 @@ static ColumnEncryptionSetting valueOfString(String value) throws SQLServerExcep enum AttestationProtocol { - HGS("HGS"); // only protocol supported currently - + HGS("HGS"), + AAS("AAS"); + private final String protocol; AttestationProtocol(String protocol) { @@ -134,7 +135,8 @@ static boolean isValidAttestationProtocol(String protocol) { enum EnclaveType { - VBS("VBS"); // only VBS type supported + VBS("VBS"), + SGX("SGX"); private final String type; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java index c9d0f90e7..422546dd5 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java @@ -2732,7 +2732,7 @@ final void doExecutePreparedStatementBatch(PrepStmtBatchExecCmd batchCommand) th boolean hasExistingTypeDefinitions = preparedTypeDefinitions != null; boolean hasNewTypeDefinitions = buildPreparedStrings(batchParam, false); - if (!isInternalEncryptionQuery && connection.isAEv2()) { + if ((0 == numBatchesExecuted) && !isInternalEncryptionQuery && connection.isAEv2()) { this.enclaveCEKs = connection.initEnclaveParameters(preparedSQL, preparedTypeDefinitions, batchParam, parameterNames); encryptionMetadataIsRetrieved = true; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java index 9ad90e981..a00c23a94 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java @@ -55,7 +55,7 @@ public class SQLServerVSMEnclaveProvider implements ISQLServerEnclaveProvider { private VSMAttestationParameters vsmParams = null; - private AttestationResponse hgsResponse = null; + private VSMAttestationResponse hgsResponse = null; private String attestationURL = null; private EnclaveSession enclaveSession = null; @@ -125,7 +125,7 @@ public byte[] getEnclavePackage(String userSQL, ArrayList enclaveCEKs) t return null; } - private AttestationResponse validateAttestationResponse(AttestationResponse ar) throws SQLServerException { + private VSMAttestationResponse validateAttestationResponse(VSMAttestationResponse ar) throws SQLServerException { try { byte[] attestationCerts = getAttestationCertificates(); ar.validateCert(attestationCerts); @@ -287,7 +287,7 @@ private ArrayList describeParameterEncryption(SQLServerConnection connec if (connection.isAEv2() && stmt.getMoreResults()) { rs = (SQLServerResultSet) stmt.getResultSet(); while (rs.next()) { - hgsResponse = new AttestationResponse(rs.getBytes(1)); + hgsResponse = new VSMAttestationResponse(rs.getBytes(1)); // This validates and establishes the enclave session if valid if (!connection.enclaveEstablished()) { hgsResponse = validateAttestationResponse(hgsResponse); @@ -311,109 +311,35 @@ private ArrayList describeParameterEncryption(SQLServerConnection connec class VSMAttestationParameters extends BaseAttestationRequest { - - // Static byte[] for VSM ECDH - private static byte ECDH_MAGIC[] = {0x45, 0x43, 0x4b, 0x33, 0x30, 0x00, 0x00, 0x00}; // Type 3 is VSM, sent as Little Endian 0x30000000 private static byte ENCLAVE_TYPE[] = new byte[] {0x3, 0x0, 0x0, 0x0}; - // VSM doesn't have a challenge - private static byte ENCLAVE_CHALLENGE[] = new byte[] {0x0, 0x0, 0x0, 0x0}; - private static int ENCLAVE_LENGTH = 104; - private byte[] x; - private byte[] y; VSMAttestationParameters() throws SQLServerException { - KeyPairGenerator kpg = null; - try { - kpg = KeyPairGenerator.getInstance("EC"); - kpg.initialize(new ECGenParameterSpec("secp384r1")); - } catch (GeneralSecurityException e) { - SQLServerException.makeFromDriverError(null, kpg, e.getLocalizedMessage(), "0", false); - } - KeyPair kp = kpg.generateKeyPair(); - ECPublicKey publicKey = (ECPublicKey) kp.getPublic(); - privateKey = kp.getPrivate(); - ECPoint w = publicKey.getW(); - x = w.getAffineX().toByteArray(); - y = w.getAffineY().toByteArray(); - - /* - * For some reason toByteArray doesn't have an Signum option like the constructor. Manually remove leading 00 - * byte if it exists. - */ - if (x[0] == 0 && x.length != 48) { - x = Arrays.copyOfRange(x, 1, x.length); - } - if (y[0] == 0 && y.length != 48) { - y = Arrays.copyOfRange(y, 1, y.length); - } + enclaveChallenge = new byte[] {0x0, 0x0, 0x0, 0x0}; + initBcryptECDH(); } @Override byte[] getBytes() { ByteArrayOutputStream os = new ByteArrayOutputStream(); os.writeBytes(ENCLAVE_TYPE); - os.writeBytes(ENCLAVE_CHALLENGE); + os.writeBytes(enclaveChallenge); os.writeBytes(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ENCLAVE_LENGTH).array()); os.writeBytes(ECDH_MAGIC); os.writeBytes(x); os.writeBytes(y); return os.toByteArray(); } - - byte[] createSessionSecret(byte[] serverResponse) throws GeneralSecurityException, SQLServerException { - if (serverResponse.length != ENCLAVE_LENGTH) { - SQLServerException.makeFromDriverError(null, this, - SQLServerResource.getResource("R_MalformedECDHPublicKey"), "0", false); - } - ByteBuffer sr = ByteBuffer.wrap(serverResponse); - byte[] magic = new byte[8]; - sr.get(magic); - if (!Arrays.equals(magic, ECDH_MAGIC)) { - SQLServerException.makeFromDriverError(null, this, SQLServerResource.getResource("R_MalformedECDHHeader"), - "0", false); - } - byte[] x = new byte[48]; - byte[] y = new byte[48]; - sr.get(x); - sr.get(y); - /* - * Server returns X and Y coordinates, create a key using the point of the server and our key parameters. - * Public/Private key parameters are the same. - */ - ECPublicKeySpec keySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(1, x), new BigInteger(1, y)), - ((ECPrivateKey) privateKey).getParams()); - KeyAgreement ka = KeyAgreement.getInstance("ECDH"); - ka.init(privateKey); - // Generate a PublicKey from the above key specifications and do an agreement with our PrivateKey - ka.doPhase(KeyFactory.getInstance("EC").generatePublic(keySpec), true); - // Generate a Secret from the agreement and hash with SHA-256 to create Session Secret - return MessageDigest.getInstance("SHA-256").digest(ka.generateSecret()); - } } @SuppressWarnings("unused") -class AttestationResponse { - private int totalSize; - private int identitySize; - private int healthReportSize; - private int enclaveReportSize; - - private byte[] enclavePK; +class VSMAttestationResponse extends BaseAttestationResponse { private byte[] healthReportCertificate; private byte[] enclaveReportPackage; - - private int sessionInfoSize; - private byte[] sessionID = new byte[8]; - private int DHPKsize; - private int DHPKSsize; - private byte[] DHpublicKey; - private byte[] publicKeySig; - private X509Certificate healthCert; - AttestationResponse(byte[] b) throws SQLServerException { + VSMAttestationResponse(byte[] b) throws SQLServerException { /*- * Parse the attestation response. * @@ -434,8 +360,8 @@ class AttestationResponse { ByteBuffer response = ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN); this.totalSize = response.getInt(); this.identitySize = response.getInt(); - this.healthReportSize = response.getInt(); - this.enclaveReportSize = response.getInt(); + int healthReportSize = response.getInt(); + int enclaveReportSize = response.getInt(); enclavePK = new byte[identitySize]; healthReportCertificate = new byte[healthReportSize]; @@ -536,54 +462,4 @@ void validateStatementSignature() throws SQLServerException, GeneralSecurityExce SQLServerResource.getResource("R_InvalidSignedStatement"), "0", false); } } - - void validateDHPublicKey() throws SQLServerException, GeneralSecurityException { - /*- - * Java doesn't directly support PKCS1 padding for RSA keys. Parse the key bytes and create a RSAPublicKeySpec - * with the exponent and modulus. - * - * Static string "RSA1" - 4B (Unused) - * Bit count - 4B (Unused) - * Public Exponent Length - 4B - * Public Modulus Length - 4B - * Prime 1 - 4B (Unused) - * Prime 2 - 4B (Unused) - * Exponent - publicExponentLength bytes - * Modulus - publicModulusLength bytes - */ - ByteBuffer enclavePKBuffer = ByteBuffer.wrap(enclavePK).order(ByteOrder.LITTLE_ENDIAN); - byte[] rsa1 = new byte[4]; - enclavePKBuffer.get(rsa1); - int bitCount = enclavePKBuffer.getInt(); - int publicExponentLength = enclavePKBuffer.getInt(); - int publicModulusLength = enclavePKBuffer.getInt(); - int prime1 = enclavePKBuffer.getInt(); - int prime2 = enclavePKBuffer.getInt(); - byte[] exponent = new byte[publicExponentLength]; - enclavePKBuffer.get(exponent); - byte[] modulus = new byte[publicModulusLength]; - enclavePKBuffer.get(modulus); - if (enclavePKBuffer.remaining() != 0) { - SQLServerException.makeFromDriverError(null, this, SQLServerResource.getResource("R_EnclavePKLengthError"), - "0", false); - } - RSAPublicKeySpec spec = new RSAPublicKeySpec(new BigInteger(1, modulus), new BigInteger(1, exponent)); - KeyFactory factory = KeyFactory.getInstance("RSA"); - PublicKey pub = factory.generatePublic(spec); - Signature sig = Signature.getInstance("SHA256withRSA"); - sig.initVerify(pub); - sig.update(DHpublicKey); - if (!sig.verify(publicKeySig)) { - SQLServerException.makeFromDriverError(null, this, SQLServerResource.getResource("R_InvalidDHKeySignature"), - "0", false); - } - } - - byte[] getDHpublicKey() { - return DHpublicKey; - } - - byte[] getSessionID() { - return sessionID; - } } From a3471ffc8f6279ee617196da159a0f6b8416faf5 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Fri, 22 Nov 2019 09:40:13 -0800 Subject: [PATCH 02/25] add caches --- .../jdbc/ISQLServerEnclaveProvider.java | 62 +++++++++++++++++-- .../jdbc/SQLServerAASEnclaveProvider.java | 60 ++++++++++++------ .../sqlserver/jdbc/SQLServerConnection.java | 12 +++- .../jdbc/SQLServerVSMEnclaveProvider.java | 11 +++- 4 files changed, 119 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java index c1890afbf..336516356 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java @@ -22,8 +22,10 @@ import java.security.spec.ECPoint; import java.security.spec.ECPublicKeySpec; import java.security.spec.RSAPublicKeySpec; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.concurrent.atomic.AtomicInteger; import javax.crypto.KeyAgreement; @@ -47,7 +49,7 @@ public interface ISQLServerEnclaveProvider { * @throws SQLServerException * when an error occurs. */ - void getAttestationParameters(boolean createNewParameters, String url) throws SQLServerException; + void getAttestationParameters(String url) throws SQLServerException; /** * Creates the enclave session @@ -81,6 +83,8 @@ ArrayList createEnclaveSession(SQLServerConnection connection, String us * @return the enclave session */ EnclaveSession getEnclaveSession(); + + void setEnclaveSession(EnclaveCacheEntry entry); } @@ -97,7 +101,7 @@ abstract class BaseAttestationRequest { byte[] getBytes() { return null; }; - + byte[] createSessionSecret(byte[] serverResponse) throws GeneralSecurityException, SQLServerException { if (serverResponse.length != ENCLAVE_LENGTH) { SQLServerException.makeFromDriverError(null, this, @@ -127,7 +131,7 @@ byte[] createSessionSecret(byte[] serverResponse) throws GeneralSecurityExceptio // Generate a Secret from the agreement and hash with SHA-256 to create Session Secret return MessageDigest.getInstance("SHA-256").digest(ka.generateSecret()); } - + void initBcryptECDH() throws SQLServerException { /* * Create our BCRYPT_ECCKEY_BLOB @@ -159,6 +163,7 @@ void initBcryptECDH() throws SQLServerException { } } + abstract class BaseAttestationResponse { protected int totalSize; protected int identitySize; @@ -172,7 +177,7 @@ abstract class BaseAttestationResponse { protected int DHPKSsize; protected byte[] DHpublicKey; protected byte[] publicKeySig; - + void validateDHPublicKey() throws SQLServerException, GeneralSecurityException { /*- * Java doesn't directly support PKCS1 padding for RSA keys. Parse the key bytes and create a RSAPublicKeySpec @@ -248,3 +253,52 @@ long getCounter() { return counter.getAndIncrement(); } } + + +final class EnclaveSessionCache { + HashMap sessionCache; + + EnclaveSessionCache() { + sessionCache = new HashMap<>(0); + } + + void addEntry(String key, BaseAttestationRequest b, EnclaveSession e) { + sessionCache.put(key, new EnclaveCacheEntry(b, e)); + } + + EnclaveCacheEntry getSession(String key) { + EnclaveCacheEntry e = sessionCache.get(key); + if (null != e && e.expired()) { + sessionCache.remove(key); + return null; + } + return e; + } +} + + +class EnclaveCacheEntry { + private static final long EIGHT_HOUR_IN_MILLIS = 28800000; + + private BaseAttestationRequest bar; + private EnclaveSession es; + private long timeCreatedInMillis; + + EnclaveCacheEntry(BaseAttestationRequest b, EnclaveSession e) { + bar = b; + es = e; + timeCreatedInMillis = Instant.now().getEpochSecond(); + } + + public boolean expired() { + return (Instant.now().getEpochSecond() - timeCreatedInMillis) > EIGHT_HOUR_IN_MILLIS; + } + + BaseAttestationRequest getBaseAttestationRequest() { + return bar; + } + + EnclaveSession getEnclaveSession() { + return es; + } +} diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java index 0f1509349..ebfe28651 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java @@ -10,38 +10,25 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.math.BigInteger; import java.net.URL; import java.net.URLConnection; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.GeneralSecurityException; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyPairGenerator; import java.security.MessageDigest; -import java.security.PublicKey; import java.security.SecureRandom; import java.security.Signature; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.ECPublicKeySpec; -import java.security.spec.RSAPublicKeySpec; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.text.MessageFormat; +import java.time.Instant; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import javax.crypto.KeyAgreement; - import org.apache.commons.codec.binary.Base64; import com.google.gson.JsonArray; @@ -64,8 +51,8 @@ public class SQLServerAASEnclaveProvider implements ISQLServerEnclaveProvider { private EnclaveSession enclaveSession = null; @Override - public void getAttestationParameters(boolean createNewParameters, String url) throws SQLServerException { - if (null == aasParams || createNewParameters) { + public void getAttestationParameters(String url) throws SQLServerException { + if (null == aasParams) { attestationURL = url; try { aasParams = new AASAttestationParameters(attestationURL); @@ -86,6 +73,7 @@ public ArrayList createEnclaveSession(SQLServerConnection connection, St try { enclaveSession = new EnclaveSession(hgsResponse.getSessionID(), aasParams.createSessionSecret(hgsResponse.getDHpublicKey())); + SQLServerConnection.enclaveCache.addEntry(connection.getEnclaveCacheHash(), aasParams, enclaveSession); } catch (GeneralSecurityException e) { SQLServerException.makeFromDriverError(connection, this, e.getLocalizedMessage(), "0", false); } @@ -301,6 +289,12 @@ private ArrayList describeParameterEncryption(SQLServerConnection connec } return enclaveRequestedCEKs; } + + @Override + public void setEnclaveSession(EnclaveCacheEntry entry) { + this.enclaveSession = entry.getEnclaveSession(); + this.aasParams = (AASAttestationParameters) entry.getBaseAttestationRequest(); + } } @@ -345,11 +339,32 @@ byte[] getNonce() { } +class JWTCertificateEntry { + private static final long TWENTY_FOUR_HOUR_IN_MILLIS = 86400000; + + private JsonArray certificates; + private long timeCreatedInMillis; + + JWTCertificateEntry(JsonArray j) { + certificates = j; + timeCreatedInMillis = Instant.now().getEpochSecond(); + } + + boolean expired() { + return (Instant.now().getEpochSecond() - timeCreatedInMillis) > TWENTY_FOUR_HOUR_IN_MILLIS; + } + + JsonArray getCertificates() { + return certificates; + } +} + + @SuppressWarnings("unused") class AASAttestationResponse extends BaseAttestationResponse { private byte[] attestationToken; - private static Map certificateCache = new HashMap<>(); + private static Map certificateCache = new HashMap<>(); AASAttestationResponse(byte[] b) throws SQLServerException { /*- @@ -410,7 +425,14 @@ void validateToken(String attestationUrl) throws SQLServerException { byte[] stmtSig = Base64.decodeBase64(splitString[2]); try { - JsonArray keys = certificateCache.get(attestationUrl); + JsonArray keys = null; + JWTCertificateEntry cacheEntry = certificateCache.get(attestationUrl); + if (null != cacheEntry && !cacheEntry.expired()) { + keys = cacheEntry.getCertificates(); + } else if (null != cacheEntry && cacheEntry.expired()) { + certificateCache.remove(attestationUrl); + } + if (null == keys) { // Use the attestation URL to find where our keys are String authorityUrl = new URL(attestationUrl).getAuthority(); @@ -424,7 +446,7 @@ void validateToken(String attestationUrl) throws SQLServerException { String jwksUrlJson = new String(jwksCon.getInputStream().readAllBytes()); JsonObject jwksJson = new JsonParser().parse(jwksUrlJson).getAsJsonObject(); keys = jwksJson.get("keys").getAsJsonArray(); - certificateCache.put(attestationUrl, keys); + certificateCache.put(attestationUrl, new JWTCertificateEntry(keys)); } // Find the specific keyID we need from our header JsonObject headerJsonObject = new JsonParser().parse(Header).getAsJsonObject(); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index c988fbf8a..bb29ac77a 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -6493,11 +6493,17 @@ boolean isAEv2() { } ISQLServerEnclaveProvider enclaveProvider; + static EnclaveSessionCache enclaveCache = new EnclaveSessionCache(); ArrayList initEnclaveParameters(String userSql, String preparedTypeDefinitions, Parameter[] params, ArrayList parameterNames) throws SQLServerException { if (!this.enclaveEstablished()) { - enclaveProvider.getAttestationParameters(false, this.enclaveAttestationUrl); + EnclaveCacheEntry entry = enclaveCache.getSession(this.getEnclaveCacheHash()); + if (null == entry) { + enclaveProvider.getAttestationParameters(this.enclaveAttestationUrl); + } else { + enclaveProvider.setEnclaveSession(entry); + } } return enclaveProvider.createEnclaveSession(this, userSql, preparedTypeDefinitions, params, parameterNames); } @@ -6509,6 +6515,10 @@ boolean enclaveEstablished() { byte[] generateEnclavePackage(String userSQL, ArrayList enclaveCEKs) throws SQLServerException { return (enclaveCEKs.size() > 0) ? enclaveProvider.getEnclavePackage(userSQL, enclaveCEKs) : null; } + + String getEnclaveCacheHash() { + return this.hostName + this.enclaveAttestationUrl; + } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java index 6b264428d..4d23cf4d8 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java @@ -46,8 +46,8 @@ public class SQLServerVSMEnclaveProvider implements ISQLServerEnclaveProvider { private EnclaveSession enclaveSession = null; @Override - public void getAttestationParameters(boolean createNewParameters, String url) throws SQLServerException { - if (null == vsmParams || createNewParameters) { + public void getAttestationParameters(String url) throws SQLServerException { + if (null == vsmParams) { attestationURL = url; vsmParams = new VSMAttestationParameters(); } @@ -63,6 +63,7 @@ public ArrayList createEnclaveSession(SQLServerConnection connection, St try { enclaveSession = new EnclaveSession(hgsResponse.getSessionID(), vsmParams.createSessionSecret(hgsResponse.getDHpublicKey())); + SQLServerConnection.enclaveCache.addEntry(connection.getEnclaveCacheHash(), vsmParams, enclaveSession); } catch (GeneralSecurityException e) { SQLServerException.makeFromDriverError(connection, this, e.getLocalizedMessage(), "0", false); } @@ -293,6 +294,12 @@ private ArrayList describeParameterEncryption(SQLServerConnection connec } return enclaveRequestedCEKs; } + + @Override + public void setEnclaveSession(EnclaveCacheEntry entry) { + this.enclaveSession = entry.getEnclaveSession(); + this.vsmParams = (VSMAttestationParameters) entry.getBaseAttestationRequest(); + } } From d8b93664fcaa4557a362e22fd54db170b40407b9 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Fri, 22 Nov 2019 09:47:08 -0800 Subject: [PATCH 03/25] Remove weird connection formatting --- .../java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index bb29ac77a..74eabc69e 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -6515,7 +6515,7 @@ boolean enclaveEstablished() { byte[] generateEnclavePackage(String userSQL, ArrayList enclaveCEKs) throws SQLServerException { return (enclaveCEKs.size() > 0) ? enclaveProvider.getEnclavePackage(userSQL, enclaveCEKs) : null; } - + String getEnclaveCacheHash() { return this.hostName + this.enclaveAttestationUrl; } From a46fa16c82ce41c5b6b856b3fb1d102953b37717 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Fri, 22 Nov 2019 09:49:07 -0800 Subject: [PATCH 04/25] format --- .../java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 74eabc69e..bb29ac77a 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -6515,7 +6515,7 @@ boolean enclaveEstablished() { byte[] generateEnclavePackage(String userSQL, ArrayList enclaveCEKs) throws SQLServerException { return (enclaveCEKs.size() > 0) ? enclaveProvider.getEnclavePackage(userSQL, enclaveCEKs) : null; } - + String getEnclaveCacheHash() { return this.hostName + this.enclaveAttestationUrl; } From b27511236c0e74668f672b905152b9fbe6a8ffe2 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Fri, 22 Nov 2019 11:14:14 -0800 Subject: [PATCH 05/25] Fix test --- .../com/microsoft/sqlserver/jdbc/EnclavePackageTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java index 8d4a21f51..1fbaa14c0 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java @@ -372,7 +372,7 @@ public static void testBadSessionSecret() throws SQLServerException { @SuppressWarnings("unused") public static void testNullAttestationResponse() throws SQLServerException { try { - AttestationResponse resp = new AttestationResponse(null); + VSMAttestationResponse resp = new VSMAttestationResponse(null); } catch (SQLServerException e) { assertTrue(e.getMessage().matches(TestUtils.formatErrorMsg("R_EnclaveResponseLengthError"))); } catch (Exception e) { @@ -387,7 +387,7 @@ public static void testNullAttestationResponse() throws SQLServerException { public static void testBadAttestationResponse() throws SQLServerException { try { byte[] responseBytes = new byte[36]; - AttestationResponse resp = new AttestationResponse(responseBytes); + VSMAttestationResponse resp = new VSMAttestationResponse(responseBytes); } catch (SQLServerException e) { assertTrue(e.getMessage().matches(TestUtils.formatErrorMsg("R_HealthCertError"))); } catch (Exception e) { @@ -400,7 +400,7 @@ public static void testBadAttestationResponse() throws SQLServerException { */ public static void testBadCertSignature() throws SQLServerException, CertificateException { try { - AttestationResponse resp = new AttestationResponse(healthReportCertificate); + VSMAttestationResponse resp = new VSMAttestationResponse(healthReportCertificate); resp.validateCert(null); } catch (SQLServerException e) { assertTrue(e.getMessage().matches(TestUtils.formatErrorMsg("R_InvalidHealthCert"))); From f11188a7c4f49476f9dfc66911ca771f89e06280 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Fri, 22 Nov 2019 12:12:59 -0800 Subject: [PATCH 06/25] Fix to connection --- .../sqlserver/jdbc/SQLServerConnection.java | 998 +++++++++--------- 1 file changed, 492 insertions(+), 506 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index bb29ac77a..2597e84d3 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -1482,626 +1482,612 @@ Connection connectInternal(Properties propsIn, + SQLServerException.getErrString("R_enclaveInvalidAttestationProtocol")); } } + } - // enclave requires columnEncryption=enabled, enclaveAttestationUrl and enclaveAttestationProtocol - if ((null != enclaveAttestationUrl && !enclaveAttestationUrl.isEmpty() - && (null == enclaveAttestationProtocol || enclaveAttestationProtocol.isEmpty())) - || (null != enclaveAttestationProtocol && !enclaveAttestationProtocol.isEmpty() - && (null == enclaveAttestationUrl || enclaveAttestationUrl.isEmpty())) - || (null != enclaveAttestationUrl && !enclaveAttestationUrl.isEmpty() - && (null != enclaveAttestationProtocol || !enclaveAttestationProtocol.isEmpty()) - && (null == columnEncryptionSetting || !isColumnEncryptionSettingEnabled()))) { - throw new SQLServerException(SQLServerException.getErrString("R_enclavePropertiesError"), null); - } + // enclave requires columnEncryption=enabled, enclaveAttestationUrl and enclaveAttestationProtocol + if ((null != enclaveAttestationUrl && !enclaveAttestationUrl.isEmpty() + && (null == enclaveAttestationProtocol || enclaveAttestationProtocol.isEmpty())) + || (null != enclaveAttestationProtocol && !enclaveAttestationProtocol.isEmpty() + && (null == enclaveAttestationUrl || enclaveAttestationUrl.isEmpty())) + || (null != enclaveAttestationUrl && !enclaveAttestationUrl.isEmpty() + && (null != enclaveAttestationProtocol || !enclaveAttestationProtocol.isEmpty()) + && (null == columnEncryptionSetting || !isColumnEncryptionSettingEnabled()))) { + throw new SQLServerException(SQLServerException.getErrString("R_enclavePropertiesError"), null); + } - sPropKey = SQLServerDriverStringProperty.KEY_STORE_AUTHENTICATION.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null != sPropValue) { - keyStoreAuthentication = KeyStoreAuthentication.valueOfString(sPropValue).toString(); - } + sPropKey = SQLServerDriverStringProperty.KEY_STORE_AUTHENTICATION.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null != sPropValue) { + keyStoreAuthentication = KeyStoreAuthentication.valueOfString(sPropValue).toString(); + } - sPropKey = SQLServerDriverStringProperty.KEY_STORE_SECRET.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null != sPropValue) { - keyStoreSecret = sPropValue; - } + sPropKey = SQLServerDriverStringProperty.KEY_STORE_SECRET.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null != sPropValue) { + keyStoreSecret = sPropValue; + } - sPropKey = SQLServerDriverStringProperty.KEY_STORE_LOCATION.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null != sPropValue) { - keyStoreLocation = sPropValue; - } + sPropKey = SQLServerDriverStringProperty.KEY_STORE_LOCATION.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null != sPropValue) { + keyStoreLocation = sPropValue; + } - registerKeyStoreProviderOnConnection(keyStoreAuthentication, keyStoreSecret, keyStoreLocation); + registerKeyStoreProviderOnConnection(keyStoreAuthentication, keyStoreSecret, keyStoreLocation); - if (null == globalCustomColumnEncryptionKeyStoreProviders) { - sPropKey = SQLServerDriverStringProperty.KEY_VAULT_PROVIDER_CLIENT_ID.toString(); + if (null == globalCustomColumnEncryptionKeyStoreProviders) { + sPropKey = SQLServerDriverStringProperty.KEY_VAULT_PROVIDER_CLIENT_ID.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null != sPropValue) { + String keyVaultColumnEncryptionProviderClientId = sPropValue; + sPropKey = SQLServerDriverStringProperty.KEY_VAULT_PROVIDER_CLIENT_KEY.toString(); sPropValue = activeConnectionProperties.getProperty(sPropKey); if (null != sPropValue) { - String keyVaultColumnEncryptionProviderClientId = sPropValue; - sPropKey = SQLServerDriverStringProperty.KEY_VAULT_PROVIDER_CLIENT_KEY.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null != sPropValue) { - String keyVaultColumnEncryptionProviderClientKey = sPropValue; - SQLServerColumnEncryptionAzureKeyVaultProvider akvProvider = new SQLServerColumnEncryptionAzureKeyVaultProvider( - keyVaultColumnEncryptionProviderClientId, - keyVaultColumnEncryptionProviderClientKey); - Map keyStoreMap = new HashMap(); - keyStoreMap.put(akvProvider.getName(), akvProvider); - registerColumnEncryptionKeyStoreProviders(keyStoreMap); - } + String keyVaultColumnEncryptionProviderClientKey = sPropValue; + SQLServerColumnEncryptionAzureKeyVaultProvider akvProvider = new SQLServerColumnEncryptionAzureKeyVaultProvider( + keyVaultColumnEncryptionProviderClientId, keyVaultColumnEncryptionProviderClientKey); + Map keyStoreMap = new HashMap(); + keyStoreMap.put(akvProvider.getName(), akvProvider); + registerColumnEncryptionKeyStoreProviders(keyStoreMap); } } + } - sPropKey = SQLServerDriverBooleanProperty.MULTI_SUBNET_FAILOVER.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null == sPropValue) { - sPropValue = Boolean - .toString(SQLServerDriverBooleanProperty.MULTI_SUBNET_FAILOVER.getDefaultValue()); - activeConnectionProperties.setProperty(sPropKey, sPropValue); - } - multiSubnetFailover = isBooleanPropertyOn(sPropKey, sPropValue); + sPropKey = SQLServerDriverBooleanProperty.MULTI_SUBNET_FAILOVER.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null == sPropValue) { + sPropValue = Boolean.toString(SQLServerDriverBooleanProperty.MULTI_SUBNET_FAILOVER.getDefaultValue()); + activeConnectionProperties.setProperty(sPropKey, sPropValue); + } + multiSubnetFailover = isBooleanPropertyOn(sPropKey, sPropValue); - sPropKey = SQLServerDriverBooleanProperty.TRANSPARENT_NETWORK_IP_RESOLUTION.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null == sPropValue) { - userSetTNIR = false; - sPropValue = Boolean.toString( - SQLServerDriverBooleanProperty.TRANSPARENT_NETWORK_IP_RESOLUTION.getDefaultValue()); - activeConnectionProperties.setProperty(sPropKey, sPropValue); - } - transparentNetworkIPResolution = isBooleanPropertyOn(sPropKey, sPropValue); + sPropKey = SQLServerDriverBooleanProperty.TRANSPARENT_NETWORK_IP_RESOLUTION.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null == sPropValue) { + userSetTNIR = false; + sPropValue = Boolean + .toString(SQLServerDriverBooleanProperty.TRANSPARENT_NETWORK_IP_RESOLUTION.getDefaultValue()); + activeConnectionProperties.setProperty(sPropKey, sPropValue); + } + transparentNetworkIPResolution = isBooleanPropertyOn(sPropKey, sPropValue); - sPropKey = SQLServerDriverBooleanProperty.ENCRYPT.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null == sPropValue) { - sPropValue = Boolean.toString(SQLServerDriverBooleanProperty.ENCRYPT.getDefaultValue()); - activeConnectionProperties.setProperty(sPropKey, sPropValue); - } + sPropKey = SQLServerDriverBooleanProperty.ENCRYPT.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null == sPropValue) { + sPropValue = Boolean.toString(SQLServerDriverBooleanProperty.ENCRYPT.getDefaultValue()); + activeConnectionProperties.setProperty(sPropKey, sPropValue); + } - // Set requestedEncryptionLevel according to the value of the encrypt connection property - requestedEncryptionLevel = isBooleanPropertyOn(sPropKey, sPropValue) ? TDS.ENCRYPT_ON : TDS.ENCRYPT_OFF; + // Set requestedEncryptionLevel according to the value of the encrypt connection property + requestedEncryptionLevel = isBooleanPropertyOn(sPropKey, sPropValue) ? TDS.ENCRYPT_ON : TDS.ENCRYPT_OFF; - sPropKey = SQLServerDriverBooleanProperty.TRUST_SERVER_CERTIFICATE.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null == sPropValue) { - sPropValue = Boolean - .toString(SQLServerDriverBooleanProperty.TRUST_SERVER_CERTIFICATE.getDefaultValue()); - activeConnectionProperties.setProperty(sPropKey, sPropValue); - } + sPropKey = SQLServerDriverBooleanProperty.TRUST_SERVER_CERTIFICATE.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null == sPropValue) { + sPropValue = Boolean + .toString(SQLServerDriverBooleanProperty.TRUST_SERVER_CERTIFICATE.getDefaultValue()); + activeConnectionProperties.setProperty(sPropKey, sPropValue); + } - trustServerCertificate = isBooleanPropertyOn(sPropKey, sPropValue); + trustServerCertificate = isBooleanPropertyOn(sPropKey, sPropValue); - trustManagerClass = activeConnectionProperties - .getProperty(SQLServerDriverStringProperty.TRUST_MANAGER_CLASS.toString()); - trustManagerConstructorArg = activeConnectionProperties - .getProperty(SQLServerDriverStringProperty.TRUST_MANAGER_CONSTRUCTOR_ARG.toString()); + trustManagerClass = activeConnectionProperties + .getProperty(SQLServerDriverStringProperty.TRUST_MANAGER_CLASS.toString()); + trustManagerConstructorArg = activeConnectionProperties + .getProperty(SQLServerDriverStringProperty.TRUST_MANAGER_CONSTRUCTOR_ARG.toString()); - sPropKey = SQLServerDriverStringProperty.SELECT_METHOD.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null == sPropValue) { - sPropValue = SQLServerDriverStringProperty.SELECT_METHOD.getDefaultValue(); - } + sPropKey = SQLServerDriverStringProperty.SELECT_METHOD.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null == sPropValue) { + sPropValue = SQLServerDriverStringProperty.SELECT_METHOD.getDefaultValue(); + } - if ("cursor".equalsIgnoreCase(sPropValue) || "direct".equalsIgnoreCase(sPropValue)) { - sPropValue = sPropValue.toLowerCase(Locale.ENGLISH); - activeConnectionProperties.setProperty(sPropKey, sPropValue); - selectMethod = sPropValue; - } else { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidselectMethod")); - Object[] msgArgs = {sPropValue}; - SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); - } + if ("cursor".equalsIgnoreCase(sPropValue) || "direct".equalsIgnoreCase(sPropValue)) { + sPropValue = sPropValue.toLowerCase(Locale.ENGLISH); + activeConnectionProperties.setProperty(sPropKey, sPropValue); + selectMethod = sPropValue; + } else { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidselectMethod")); + Object[] msgArgs = {sPropValue}; + SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); + } - sPropKey = SQLServerDriverStringProperty.RESPONSE_BUFFERING.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null == sPropValue) { - sPropValue = SQLServerDriverStringProperty.RESPONSE_BUFFERING.getDefaultValue(); - } + sPropKey = SQLServerDriverStringProperty.RESPONSE_BUFFERING.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null == sPropValue) { + sPropValue = SQLServerDriverStringProperty.RESPONSE_BUFFERING.getDefaultValue(); + } - if ("full".equalsIgnoreCase(sPropValue) || "adaptive".equalsIgnoreCase(sPropValue)) { - activeConnectionProperties.setProperty(sPropKey, sPropValue.toLowerCase(Locale.ENGLISH)); - } else { - MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_invalidresponseBuffering")); - Object[] msgArgs = {sPropValue}; - SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); - } + if ("full".equalsIgnoreCase(sPropValue) || "adaptive".equalsIgnoreCase(sPropValue)) { + activeConnectionProperties.setProperty(sPropKey, sPropValue.toLowerCase(Locale.ENGLISH)); + } else { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidresponseBuffering")); + Object[] msgArgs = {sPropValue}; + SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); + } - sPropKey = SQLServerDriverStringProperty.APPLICATION_INTENT.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null == sPropValue) { - sPropValue = SQLServerDriverStringProperty.APPLICATION_INTENT.getDefaultValue(); - } + sPropKey = SQLServerDriverStringProperty.APPLICATION_INTENT.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null == sPropValue) { + sPropValue = SQLServerDriverStringProperty.APPLICATION_INTENT.getDefaultValue(); + } - applicationIntent = ApplicationIntent.valueOfString(sPropValue); - activeConnectionProperties.setProperty(sPropKey, applicationIntent.toString()); + applicationIntent = ApplicationIntent.valueOfString(sPropValue); + activeConnectionProperties.setProperty(sPropKey, applicationIntent.toString()); - sPropKey = SQLServerDriverBooleanProperty.SEND_TIME_AS_DATETIME.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null == sPropValue) { - sPropValue = Boolean - .toString(SQLServerDriverBooleanProperty.SEND_TIME_AS_DATETIME.getDefaultValue()); - activeConnectionProperties.setProperty(sPropKey, sPropValue); - } + sPropKey = SQLServerDriverBooleanProperty.SEND_TIME_AS_DATETIME.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null == sPropValue) { + sPropValue = Boolean.toString(SQLServerDriverBooleanProperty.SEND_TIME_AS_DATETIME.getDefaultValue()); + activeConnectionProperties.setProperty(sPropKey, sPropValue); + } - sendTimeAsDatetime = isBooleanPropertyOn(sPropKey, sPropValue); + sendTimeAsDatetime = isBooleanPropertyOn(sPropKey, sPropValue); - sPropKey = SQLServerDriverBooleanProperty.USE_FMT_ONLY.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null == sPropValue) { - sPropValue = Boolean.toString(SQLServerDriverBooleanProperty.USE_FMT_ONLY.getDefaultValue()); - activeConnectionProperties.setProperty(sPropKey, sPropValue); - } - useFmtOnly = isBooleanPropertyOn(sPropKey, sPropValue); + sPropKey = SQLServerDriverBooleanProperty.USE_FMT_ONLY.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null == sPropValue) { + sPropValue = Boolean.toString(SQLServerDriverBooleanProperty.USE_FMT_ONLY.getDefaultValue()); + activeConnectionProperties.setProperty(sPropKey, sPropValue); + } + useFmtOnly = isBooleanPropertyOn(sPropKey, sPropValue); - // Must be set before DISABLE_STATEMENT_POOLING - sPropKey = SQLServerDriverIntProperty.STATEMENT_POOLING_CACHE_SIZE.toString(); - if (activeConnectionProperties.getProperty(sPropKey) != null - && activeConnectionProperties.getProperty(sPropKey).length() > 0) { - try { - int n = Integer.parseInt(activeConnectionProperties.getProperty(sPropKey)); - this.setStatementPoolingCacheSize(n); - } catch (NumberFormatException e) { - MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_statementPoolingCacheSize")); - Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)}; - SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); - } + // Must be set before DISABLE_STATEMENT_POOLING + sPropKey = SQLServerDriverIntProperty.STATEMENT_POOLING_CACHE_SIZE.toString(); + if (activeConnectionProperties.getProperty(sPropKey) != null + && activeConnectionProperties.getProperty(sPropKey).length() > 0) { + try { + int n = Integer.parseInt(activeConnectionProperties.getProperty(sPropKey)); + this.setStatementPoolingCacheSize(n); + } catch (NumberFormatException e) { + MessageFormat form = new MessageFormat( + SQLServerException.getErrString("R_statementPoolingCacheSize")); + Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)}; + SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); } + } - // Must be set after STATEMENT_POOLING_CACHE_SIZE - sPropKey = SQLServerDriverBooleanProperty.DISABLE_STATEMENT_POOLING.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null != sPropValue) { - setDisableStatementPooling(isBooleanPropertyOn(sPropKey, sPropValue)); - } + // Must be set after STATEMENT_POOLING_CACHE_SIZE + sPropKey = SQLServerDriverBooleanProperty.DISABLE_STATEMENT_POOLING.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null != sPropValue) { + setDisableStatementPooling(isBooleanPropertyOn(sPropKey, sPropValue)); + } + + sPropKey = SQLServerDriverBooleanProperty.INTEGRATED_SECURITY.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null != sPropValue) { + integratedSecurity = isBooleanPropertyOn(sPropKey, sPropValue); + } - sPropKey = SQLServerDriverBooleanProperty.INTEGRATED_SECURITY.toString(); + // Ignore authenticationScheme setting if integrated authentication not specified + if (integratedSecurity) { + sPropKey = SQLServerDriverStringProperty.AUTHENTICATION_SCHEME.toString(); sPropValue = activeConnectionProperties.getProperty(sPropKey); if (null != sPropValue) { - integratedSecurity = isBooleanPropertyOn(sPropKey, sPropValue); + intAuthScheme = AuthenticationScheme.valueOfString(sPropValue); } + } - // Ignore authenticationScheme setting if integrated authentication not specified - if (integratedSecurity) { - sPropKey = SQLServerDriverStringProperty.AUTHENTICATION_SCHEME.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null != sPropValue) { - intAuthScheme = AuthenticationScheme.valueOfString(sPropValue); - } + if (intAuthScheme == AuthenticationScheme.javaKerberos) { + sPropKey = SQLServerDriverObjectProperty.GSS_CREDENTIAL.toString(); + if (activeConnectionProperties.containsKey(sPropKey)) { + impersonatedUserCred = (GSSCredential) activeConnectionProperties.get(sPropKey); + isUserCreatedCredential = true; } - - if (intAuthScheme == AuthenticationScheme.javaKerberos) { - sPropKey = SQLServerDriverObjectProperty.GSS_CREDENTIAL.toString(); - if (activeConnectionProperties.containsKey(sPropKey)) { - impersonatedUserCred = (GSSCredential) activeConnectionProperties.get(sPropKey); - isUserCreatedCredential = true; - } - } else if (intAuthScheme == AuthenticationScheme.ntlm) { - String sPropKeyDomain = SQLServerDriverStringProperty.DOMAIN.toString(); - String sPropValueDomain = activeConnectionProperties.getProperty(sPropKeyDomain); - if (null == sPropValueDomain) { - activeConnectionProperties.setProperty(sPropKeyDomain, - SQLServerDriverStringProperty.DOMAIN.getDefaultValue()); - } - - // NTLM and no user or password - if (activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString()).isEmpty() - || activeConnectionProperties.getProperty(SQLServerDriverStringProperty.PASSWORD.toString()) - .isEmpty()) { - - if (connectionlogger.isLoggable(Level.SEVERE)) { - connectionlogger.severe( - toString() + " " + SQLServerException.getErrString("R_NtlmNoUserPasswordDomain")); - } - throw new SQLServerException(SQLServerException.getErrString("R_NtlmNoUserPasswordDomain"), - null); - } - ntlmAuthentication = true; + } else if (intAuthScheme == AuthenticationScheme.ntlm) { + String sPropKeyDomain = SQLServerDriverStringProperty.DOMAIN.toString(); + String sPropValueDomain = activeConnectionProperties.getProperty(sPropKeyDomain); + if (null == sPropValueDomain) { + activeConnectionProperties.setProperty(sPropKeyDomain, + SQLServerDriverStringProperty.DOMAIN.getDefaultValue()); } - sPropKey = SQLServerDriverStringProperty.AUTHENTICATION.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null == sPropValue) { - sPropValue = SQLServerDriverStringProperty.AUTHENTICATION.getDefaultValue(); - } - authenticationString = SqlAuthentication.valueOfString(sPropValue).toString().trim(); + // NTLM and no user or password + if (activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString()).isEmpty() + || activeConnectionProperties.getProperty(SQLServerDriverStringProperty.PASSWORD.toString()) + .isEmpty()) { - if (integratedSecurity - && !authenticationString.equalsIgnoreCase(SqlAuthentication.NotSpecified.toString())) { if (connectionlogger.isLoggable(Level.SEVERE)) { - connectionlogger.severe(toString() + " " - + SQLServerException.getErrString("R_SetAuthenticationWhenIntegratedSecurityTrue")); + connectionlogger.severe( + toString() + " " + SQLServerException.getErrString("R_NtlmNoUserPasswordDomain")); } - throw new SQLServerException( - SQLServerException.getErrString("R_SetAuthenticationWhenIntegratedSecurityTrue"), null); + throw new SQLServerException(SQLServerException.getErrString("R_NtlmNoUserPasswordDomain"), null); } + ntlmAuthentication = true; + } - if (authenticationString.equalsIgnoreCase(SqlAuthentication.ActiveDirectoryIntegrated.toString()) - && ((!activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString()) - .isEmpty()) - || (!activeConnectionProperties - .getProperty(SQLServerDriverStringProperty.PASSWORD.toString()).isEmpty()))) { - if (connectionlogger.isLoggable(Level.SEVERE)) { - connectionlogger.severe(toString() + " " - + SQLServerException.getErrString("R_IntegratedAuthenticationWithUserPassword")); - } - throw new SQLServerException( - SQLServerException.getErrString("R_IntegratedAuthenticationWithUserPassword"), null); - } + sPropKey = SQLServerDriverStringProperty.AUTHENTICATION.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null == sPropValue) { + sPropValue = SQLServerDriverStringProperty.AUTHENTICATION.getDefaultValue(); + } + authenticationString = SqlAuthentication.valueOfString(sPropValue).toString().trim(); - if (authenticationString.equalsIgnoreCase(SqlAuthentication.ActiveDirectoryPassword.toString()) - && ((activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString()) - .isEmpty()) - || (activeConnectionProperties - .getProperty(SQLServerDriverStringProperty.PASSWORD.toString()).isEmpty()))) { - if (connectionlogger.isLoggable(Level.SEVERE)) { - connectionlogger.severe(toString() + " " - + SQLServerException.getErrString("R_NoUserPasswordForActivePassword")); - } - throw new SQLServerException(SQLServerException.getErrString("R_NoUserPasswordForActivePassword"), - null); + if (integratedSecurity + && !authenticationString.equalsIgnoreCase(SqlAuthentication.NotSpecified.toString())) { + if (connectionlogger.isLoggable(Level.SEVERE)) { + connectionlogger.severe(toString() + " " + + SQLServerException.getErrString("R_SetAuthenticationWhenIntegratedSecurityTrue")); } + throw new SQLServerException( + SQLServerException.getErrString("R_SetAuthenticationWhenIntegratedSecurityTrue"), null); + } - if (authenticationString.equalsIgnoreCase(SqlAuthentication.ActiveDirectoryMSI.toString()) - && ((!activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString()) - .isEmpty()) - || (!activeConnectionProperties - .getProperty(SQLServerDriverStringProperty.PASSWORD.toString()).isEmpty()))) { - if (connectionlogger.isLoggable(Level.SEVERE)) { - connectionlogger.severe(toString() + " " - + SQLServerException.getErrString("R_MSIAuthenticationWithUserPassword")); - } - throw new SQLServerException(SQLServerException.getErrString("R_MSIAuthenticationWithUserPassword"), - null); - } + if (authenticationString.equalsIgnoreCase(SqlAuthentication.ActiveDirectoryIntegrated.toString()) + && ((!activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString()) + .isEmpty()) + || (!activeConnectionProperties + .getProperty(SQLServerDriverStringProperty.PASSWORD.toString()).isEmpty()))) { + if (connectionlogger.isLoggable(Level.SEVERE)) { + connectionlogger.severe(toString() + " " + + SQLServerException.getErrString("R_IntegratedAuthenticationWithUserPassword")); + } + throw new SQLServerException( + SQLServerException.getErrString("R_IntegratedAuthenticationWithUserPassword"), null); + } - if (authenticationString.equalsIgnoreCase(SqlAuthentication.SqlPassword.toString()) - && ((activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString()) - .isEmpty()) - || (activeConnectionProperties - .getProperty(SQLServerDriverStringProperty.PASSWORD.toString()).isEmpty()))) { - if (connectionlogger.isLoggable(Level.SEVERE)) { - connectionlogger.severe( - toString() + " " + SQLServerException.getErrString("R_NoUserPasswordForSqlPassword")); - } - throw new SQLServerException(SQLServerException.getErrString("R_NoUserPasswordForSqlPassword"), - null); - } + if (authenticationString.equalsIgnoreCase(SqlAuthentication.ActiveDirectoryPassword.toString()) + && ((activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString()) + .isEmpty()) + || (activeConnectionProperties + .getProperty(SQLServerDriverStringProperty.PASSWORD.toString()).isEmpty()))) { + if (connectionlogger.isLoggable(Level.SEVERE)) { + connectionlogger.severe( + toString() + " " + SQLServerException.getErrString("R_NoUserPasswordForActivePassword")); + } + throw new SQLServerException(SQLServerException.getErrString("R_NoUserPasswordForActivePassword"), + null); + } - sPropKey = SQLServerDriverStringProperty.ACCESS_TOKEN.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null != sPropValue) { - accessTokenInByte = sPropValue.getBytes(UTF_16LE); - } + if (authenticationString.equalsIgnoreCase(SqlAuthentication.ActiveDirectoryMSI.toString()) + && ((!activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString()) + .isEmpty()) + || (!activeConnectionProperties + .getProperty(SQLServerDriverStringProperty.PASSWORD.toString()).isEmpty()))) { + if (connectionlogger.isLoggable(Level.SEVERE)) { + connectionlogger.severe( + toString() + " " + SQLServerException.getErrString("R_MSIAuthenticationWithUserPassword")); + } + throw new SQLServerException(SQLServerException.getErrString("R_MSIAuthenticationWithUserPassword"), + null); + } - if ((null != accessTokenInByte) && 0 == accessTokenInByte.length) { - if (connectionlogger.isLoggable(Level.SEVERE)) { - connectionlogger.severe( - toString() + " " + SQLServerException.getErrString("R_AccessTokenCannotBeEmpty")); - } - throw new SQLServerException(SQLServerException.getErrString("R_AccessTokenCannotBeEmpty"), null); + if (authenticationString.equalsIgnoreCase(SqlAuthentication.SqlPassword.toString()) + && ((activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString()) + .isEmpty()) + || (activeConnectionProperties + .getProperty(SQLServerDriverStringProperty.PASSWORD.toString()).isEmpty()))) { + if (connectionlogger.isLoggable(Level.SEVERE)) { + connectionlogger.severe( + toString() + " " + SQLServerException.getErrString("R_NoUserPasswordForSqlPassword")); } + throw new SQLServerException(SQLServerException.getErrString("R_NoUserPasswordForSqlPassword"), null); + } - if (integratedSecurity && (null != accessTokenInByte)) { - if (connectionlogger.isLoggable(Level.SEVERE)) { - connectionlogger.severe(toString() + " " - + SQLServerException.getErrString("R_SetAccesstokenWhenIntegratedSecurityTrue")); - } - throw new SQLServerException( - SQLServerException.getErrString("R_SetAccesstokenWhenIntegratedSecurityTrue"), null); + sPropKey = SQLServerDriverStringProperty.ACCESS_TOKEN.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null != sPropValue) { + accessTokenInByte = sPropValue.getBytes(UTF_16LE); + } + + if ((null != accessTokenInByte) && 0 == accessTokenInByte.length) { + if (connectionlogger.isLoggable(Level.SEVERE)) { + connectionlogger + .severe(toString() + " " + SQLServerException.getErrString("R_AccessTokenCannotBeEmpty")); } + throw new SQLServerException(SQLServerException.getErrString("R_AccessTokenCannotBeEmpty"), null); + } - if ((!authenticationString.equalsIgnoreCase(SqlAuthentication.NotSpecified.toString())) - && (null != accessTokenInByte)) { - if (connectionlogger.isLoggable(Level.SEVERE)) { - connectionlogger.severe(toString() + " " - + SQLServerException.getErrString("R_SetBothAuthenticationAndAccessToken")); - } - throw new SQLServerException( - SQLServerException.getErrString("R_SetBothAuthenticationAndAccessToken"), null); + if (integratedSecurity && (null != accessTokenInByte)) { + if (connectionlogger.isLoggable(Level.SEVERE)) { + connectionlogger.severe(toString() + " " + + SQLServerException.getErrString("R_SetAccesstokenWhenIntegratedSecurityTrue")); } + throw new SQLServerException( + SQLServerException.getErrString("R_SetAccesstokenWhenIntegratedSecurityTrue"), null); + } - if ((null != accessTokenInByte) && ((!activeConnectionProperties - .getProperty(SQLServerDriverStringProperty.USER.toString()).isEmpty()) - || (!activeConnectionProperties.getProperty(SQLServerDriverStringProperty.PASSWORD.toString()) - .isEmpty()))) { - if (connectionlogger.isLoggable(Level.SEVERE)) { - connectionlogger.severe( - toString() + " " + SQLServerException.getErrString("R_AccessTokenWithUserPassword")); - } - throw new SQLServerException(SQLServerException.getErrString("R_AccessTokenWithUserPassword"), - null); + if ((!authenticationString.equalsIgnoreCase(SqlAuthentication.NotSpecified.toString())) + && (null != accessTokenInByte)) { + if (connectionlogger.isLoggable(Level.SEVERE)) { + connectionlogger.severe(toString() + " " + + SQLServerException.getErrString("R_SetBothAuthenticationAndAccessToken")); } + throw new SQLServerException(SQLServerException.getErrString("R_SetBothAuthenticationAndAccessToken"), + null); + } - // Turn off TNIR for FedAuth if user did not set TNIR explicitly - if (!userSetTNIR && (!authenticationString.equalsIgnoreCase(SqlAuthentication.NotSpecified.toString()) - || null != accessTokenInByte)) { - transparentNetworkIPResolution = false; + if ((null != accessTokenInByte) && ((!activeConnectionProperties + .getProperty(SQLServerDriverStringProperty.USER.toString()).isEmpty()) + || (!activeConnectionProperties.getProperty(SQLServerDriverStringProperty.PASSWORD.toString()) + .isEmpty()))) { + if (connectionlogger.isLoggable(Level.SEVERE)) { + connectionlogger.severe( + toString() + " " + SQLServerException.getErrString("R_AccessTokenWithUserPassword")); } + throw new SQLServerException(SQLServerException.getErrString("R_AccessTokenWithUserPassword"), null); + } - sPropKey = SQLServerDriverStringProperty.WORKSTATION_ID.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - validateMaxSQLLoginName(sPropKey, sPropValue); + // Turn off TNIR for FedAuth if user did not set TNIR explicitly + if (!userSetTNIR && (!authenticationString.equalsIgnoreCase(SqlAuthentication.NotSpecified.toString()) + || null != accessTokenInByte)) { + transparentNetworkIPResolution = false; + } - int nPort = 0; - sPropKey = SQLServerDriverIntProperty.PORT_NUMBER.toString(); - try { - String strPort = activeConnectionProperties.getProperty(sPropKey); - if (null != strPort) { - nPort = Integer.parseInt(strPort); + sPropKey = SQLServerDriverStringProperty.WORKSTATION_ID.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + validateMaxSQLLoginName(sPropKey, sPropValue); - if ((nPort < 0) || (nPort > 65535)) { - MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_invalidPortNumber")); - Object[] msgArgs = {Integer.toString(nPort)}; - SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); - } + int nPort = 0; + sPropKey = SQLServerDriverIntProperty.PORT_NUMBER.toString(); + try { + String strPort = activeConnectionProperties.getProperty(sPropKey); + if (null != strPort) { + nPort = Integer.parseInt(strPort); + + if ((nPort < 0) || (nPort > 65535)) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPortNumber")); + Object[] msgArgs = {Integer.toString(nPort)}; + SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); } - } catch (NumberFormatException e) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPortNumber")); - Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)}; - SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); } + } catch (NumberFormatException e) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPortNumber")); + Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)}; + SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); + } - // Handle optional packetSize property - sPropKey = SQLServerDriverIntProperty.PACKET_SIZE.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null != sPropValue && sPropValue.length() > 0) { - try { - requestedPacketSize = Integer.parseInt(sPropValue); - - // -1 --> Use server default - if (-1 == requestedPacketSize) - requestedPacketSize = TDS.SERVER_PACKET_SIZE; - - // 0 --> Use maximum size - else if (0 == requestedPacketSize) - requestedPacketSize = TDS.MAX_PACKET_SIZE; - } catch (NumberFormatException e) { - // Ensure that an invalid prop value results in an invalid packet size that - // is not acceptable to the server. - requestedPacketSize = TDS.INVALID_PACKET_SIZE; - } + // Handle optional packetSize property + sPropKey = SQLServerDriverIntProperty.PACKET_SIZE.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null != sPropValue && sPropValue.length() > 0) { + try { + requestedPacketSize = Integer.parseInt(sPropValue); - if (TDS.SERVER_PACKET_SIZE != requestedPacketSize) { - // Complain if the packet size is not in the range acceptable to the server. - if (requestedPacketSize < TDS.MIN_PACKET_SIZE || requestedPacketSize > TDS.MAX_PACKET_SIZE) { - MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_invalidPacketSize")); - Object[] msgArgs = {sPropValue}; - SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); - } + // -1 --> Use server default + if (-1 == requestedPacketSize) + requestedPacketSize = TDS.SERVER_PACKET_SIZE; + + // 0 --> Use maximum size + else if (0 == requestedPacketSize) + requestedPacketSize = TDS.MAX_PACKET_SIZE; + } catch (NumberFormatException e) { + // Ensure that an invalid prop value results in an invalid packet size that + // is not acceptable to the server. + requestedPacketSize = TDS.INVALID_PACKET_SIZE; + } + + if (TDS.SERVER_PACKET_SIZE != requestedPacketSize) { + // Complain if the packet size is not in the range acceptable to the server. + if (requestedPacketSize < TDS.MIN_PACKET_SIZE || requestedPacketSize > TDS.MAX_PACKET_SIZE) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPacketSize")); + Object[] msgArgs = {sPropValue}; + SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); } } + } - // Note isBooleanPropertyOn will throw exception if parsed value is not valid. - - // have to check for null before calling isBooleanPropertyOn, because isBooleanPropertyOn - // assumes that the null property defaults to false. - sPropKey = SQLServerDriverBooleanProperty.SEND_STRING_PARAMETERS_AS_UNICODE.toString(); - sendStringParametersAsUnicode = (null == activeConnectionProperties.getProperty( - sPropKey)) ? SQLServerDriverBooleanProperty.SEND_STRING_PARAMETERS_AS_UNICODE.getDefaultValue() - : isBooleanPropertyOn(sPropKey, activeConnectionProperties.getProperty(sPropKey)); - - sPropKey = SQLServerDriverBooleanProperty.LAST_UPDATE_COUNT.toString(); - lastUpdateCount = isBooleanPropertyOn(sPropKey, activeConnectionProperties.getProperty(sPropKey)); - sPropKey = SQLServerDriverBooleanProperty.XOPEN_STATES.toString(); - xopenStates = isBooleanPropertyOn(sPropKey, activeConnectionProperties.getProperty(sPropKey)); - - sPropKey = SQLServerDriverStringProperty.RESPONSE_BUFFERING.toString(); - responseBuffering = (null != activeConnectionProperties.getProperty(sPropKey) - && activeConnectionProperties.getProperty(sPropKey).length() > 0) - ? activeConnectionProperties - .getProperty(sPropKey) - : null; - - sPropKey = SQLServerDriverIntProperty.LOCK_TIMEOUT.toString(); - int defaultLockTimeOut = SQLServerDriverIntProperty.LOCK_TIMEOUT.getDefaultValue(); - nLockTimeout = defaultLockTimeOut; // Wait forever - if (activeConnectionProperties.getProperty(sPropKey) != null - && activeConnectionProperties.getProperty(sPropKey).length() > 0) { - try { - int n = Integer.parseInt(activeConnectionProperties.getProperty(sPropKey)); - if (n >= defaultLockTimeOut) - nLockTimeout = n; - else { - MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_invalidLockTimeOut")); - Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)}; - SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); - } - } catch (NumberFormatException e) { + // Note isBooleanPropertyOn will throw exception if parsed value is not valid. + + // have to check for null before calling isBooleanPropertyOn, because isBooleanPropertyOn + // assumes that the null property defaults to false. + sPropKey = SQLServerDriverBooleanProperty.SEND_STRING_PARAMETERS_AS_UNICODE.toString(); + sendStringParametersAsUnicode = (null == activeConnectionProperties.getProperty( + sPropKey)) ? SQLServerDriverBooleanProperty.SEND_STRING_PARAMETERS_AS_UNICODE.getDefaultValue() + : isBooleanPropertyOn(sPropKey, activeConnectionProperties.getProperty(sPropKey)); + + sPropKey = SQLServerDriverBooleanProperty.LAST_UPDATE_COUNT.toString(); + lastUpdateCount = isBooleanPropertyOn(sPropKey, activeConnectionProperties.getProperty(sPropKey)); + sPropKey = SQLServerDriverBooleanProperty.XOPEN_STATES.toString(); + xopenStates = isBooleanPropertyOn(sPropKey, activeConnectionProperties.getProperty(sPropKey)); + + sPropKey = SQLServerDriverStringProperty.RESPONSE_BUFFERING.toString(); + responseBuffering = (null != activeConnectionProperties.getProperty(sPropKey) + && activeConnectionProperties.getProperty(sPropKey).length() > 0) + ? activeConnectionProperties + .getProperty(sPropKey) + : null; + + sPropKey = SQLServerDriverIntProperty.LOCK_TIMEOUT.toString(); + int defaultLockTimeOut = SQLServerDriverIntProperty.LOCK_TIMEOUT.getDefaultValue(); + nLockTimeout = defaultLockTimeOut; // Wait forever + if (activeConnectionProperties.getProperty(sPropKey) != null + && activeConnectionProperties.getProperty(sPropKey).length() > 0) { + try { + int n = Integer.parseInt(activeConnectionProperties.getProperty(sPropKey)); + if (n >= defaultLockTimeOut) + nLockTimeout = n; + else { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidLockTimeOut")); Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)}; SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); } + } catch (NumberFormatException e) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidLockTimeOut")); + Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)}; + SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); } + } - sPropKey = SQLServerDriverIntProperty.QUERY_TIMEOUT.toString(); - int defaultQueryTimeout = SQLServerDriverIntProperty.QUERY_TIMEOUT.getDefaultValue(); - queryTimeoutSeconds = defaultQueryTimeout; // Wait forever - if (activeConnectionProperties.getProperty(sPropKey) != null - && activeConnectionProperties.getProperty(sPropKey).length() > 0) { - try { - int n = Integer.parseInt(activeConnectionProperties.getProperty(sPropKey)); - if (n >= defaultQueryTimeout) { - queryTimeoutSeconds = n; - } else { - MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_invalidQueryTimeout")); - Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)}; - SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); - } - } catch (NumberFormatException e) { + sPropKey = SQLServerDriverIntProperty.QUERY_TIMEOUT.toString(); + int defaultQueryTimeout = SQLServerDriverIntProperty.QUERY_TIMEOUT.getDefaultValue(); + queryTimeoutSeconds = defaultQueryTimeout; // Wait forever + if (activeConnectionProperties.getProperty(sPropKey) != null + && activeConnectionProperties.getProperty(sPropKey).length() > 0) { + try { + int n = Integer.parseInt(activeConnectionProperties.getProperty(sPropKey)); + if (n >= defaultQueryTimeout) { + queryTimeoutSeconds = n; + } else { MessageFormat form = new MessageFormat( SQLServerException.getErrString("R_invalidQueryTimeout")); Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)}; SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); } + } catch (NumberFormatException e) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidQueryTimeout")); + Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)}; + SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); } + } - sPropKey = SQLServerDriverIntProperty.SOCKET_TIMEOUT.toString(); - int defaultSocketTimeout = SQLServerDriverIntProperty.SOCKET_TIMEOUT.getDefaultValue(); - socketTimeoutMilliseconds = defaultSocketTimeout; // Wait forever - if (activeConnectionProperties.getProperty(sPropKey) != null - && activeConnectionProperties.getProperty(sPropKey).length() > 0) { - try { - int n = Integer.parseInt(activeConnectionProperties.getProperty(sPropKey)); - if (n >= defaultSocketTimeout) { - socketTimeoutMilliseconds = n; - } else { - MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_invalidSocketTimeout")); - Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)}; - SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); - } - } catch (NumberFormatException e) { + sPropKey = SQLServerDriverIntProperty.SOCKET_TIMEOUT.toString(); + int defaultSocketTimeout = SQLServerDriverIntProperty.SOCKET_TIMEOUT.getDefaultValue(); + socketTimeoutMilliseconds = defaultSocketTimeout; // Wait forever + if (activeConnectionProperties.getProperty(sPropKey) != null + && activeConnectionProperties.getProperty(sPropKey).length() > 0) { + try { + int n = Integer.parseInt(activeConnectionProperties.getProperty(sPropKey)); + if (n >= defaultSocketTimeout) { + socketTimeoutMilliseconds = n; + } else { MessageFormat form = new MessageFormat( SQLServerException.getErrString("R_invalidSocketTimeout")); Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)}; SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); } + } catch (NumberFormatException e) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidSocketTimeout")); + Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)}; + SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); } + } - sPropKey = SQLServerDriverIntProperty.CANCEL_QUERY_TIMEOUT.toString(); - int cancelQueryTimeout = SQLServerDriverIntProperty.CANCEL_QUERY_TIMEOUT.getDefaultValue(); + sPropKey = SQLServerDriverIntProperty.CANCEL_QUERY_TIMEOUT.toString(); + int cancelQueryTimeout = SQLServerDriverIntProperty.CANCEL_QUERY_TIMEOUT.getDefaultValue(); - if (activeConnectionProperties.getProperty(sPropKey) != null - && activeConnectionProperties.getProperty(sPropKey).length() > 0) { - try { - int n = Integer.parseInt(activeConnectionProperties.getProperty(sPropKey)); - if (n >= cancelQueryTimeout) { - // use cancelQueryTimeout only if queryTimeout is set. - if (queryTimeoutSeconds > defaultQueryTimeout) { - cancelQueryTimeoutSeconds = n; - } - } else { - MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_invalidCancelQueryTimeout")); - Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)}; - SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); + if (activeConnectionProperties.getProperty(sPropKey) != null + && activeConnectionProperties.getProperty(sPropKey).length() > 0) { + try { + int n = Integer.parseInt(activeConnectionProperties.getProperty(sPropKey)); + if (n >= cancelQueryTimeout) { + // use cancelQueryTimeout only if queryTimeout is set. + if (queryTimeoutSeconds > defaultQueryTimeout) { + cancelQueryTimeoutSeconds = n; } - } catch (NumberFormatException e) { + } else { MessageFormat form = new MessageFormat( SQLServerException.getErrString("R_invalidCancelQueryTimeout")); Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)}; SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); } + } catch (NumberFormatException e) { + MessageFormat form = new MessageFormat( + SQLServerException.getErrString("R_invalidCancelQueryTimeout")); + Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)}; + SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); } + } - sPropKey = SQLServerDriverIntProperty.SERVER_PREPARED_STATEMENT_DISCARD_THRESHOLD.toString(); - if (activeConnectionProperties.getProperty(sPropKey) != null - && activeConnectionProperties.getProperty(sPropKey).length() > 0) { - try { - int n = Integer.parseInt(activeConnectionProperties.getProperty(sPropKey)); - setServerPreparedStatementDiscardThreshold(n); - } catch (NumberFormatException e) { - MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_serverPreparedStatementDiscardThreshold")); - Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)}; - SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); - } + sPropKey = SQLServerDriverIntProperty.SERVER_PREPARED_STATEMENT_DISCARD_THRESHOLD.toString(); + if (activeConnectionProperties.getProperty(sPropKey) != null + && activeConnectionProperties.getProperty(sPropKey).length() > 0) { + try { + int n = Integer.parseInt(activeConnectionProperties.getProperty(sPropKey)); + setServerPreparedStatementDiscardThreshold(n); + } catch (NumberFormatException e) { + MessageFormat form = new MessageFormat( + SQLServerException.getErrString("R_serverPreparedStatementDiscardThreshold")); + Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)}; + SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); } + } - sPropKey = SQLServerDriverBooleanProperty.ENABLE_PREPARE_ON_FIRST_PREPARED_STATEMENT.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null != sPropValue) { - setEnablePrepareOnFirstPreparedStatementCall(isBooleanPropertyOn(sPropKey, sPropValue)); - } + sPropKey = SQLServerDriverBooleanProperty.ENABLE_PREPARE_ON_FIRST_PREPARED_STATEMENT.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null != sPropValue) { + setEnablePrepareOnFirstPreparedStatementCall(isBooleanPropertyOn(sPropKey, sPropValue)); + } - sPropKey = SQLServerDriverBooleanProperty.USE_BULK_COPY_FOR_BATCH_INSERT.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null != sPropValue) { - useBulkCopyForBatchInsert = isBooleanPropertyOn(sPropKey, sPropValue); - } + sPropKey = SQLServerDriverBooleanProperty.USE_BULK_COPY_FOR_BATCH_INSERT.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null != sPropValue) { + useBulkCopyForBatchInsert = isBooleanPropertyOn(sPropKey, sPropValue); + } - sPropKey = SQLServerDriverStringProperty.SSL_PROTOCOL.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null == sPropValue) { - sPropValue = SQLServerDriverStringProperty.SSL_PROTOCOL.getDefaultValue(); - activeConnectionProperties.setProperty(sPropKey, sPropValue); - } else { - activeConnectionProperties.setProperty(sPropKey, SSLProtocol.valueOfString(sPropValue).toString()); - } + sPropKey = SQLServerDriverStringProperty.SSL_PROTOCOL.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null == sPropValue) { + sPropValue = SQLServerDriverStringProperty.SSL_PROTOCOL.getDefaultValue(); + activeConnectionProperties.setProperty(sPropKey, sPropValue); + } else { + activeConnectionProperties.setProperty(sPropKey, SSLProtocol.valueOfString(sPropValue).toString()); + } - sPropKey = SQLServerDriverStringProperty.MSI_CLIENT_ID.toString(); - sPropValue = activeConnectionProperties.getProperty(sPropKey); - if (null != sPropValue) { - activeConnectionProperties.setProperty(sPropKey, sPropValue); - } + sPropKey = SQLServerDriverStringProperty.MSI_CLIENT_ID.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null != sPropValue) { + activeConnectionProperties.setProperty(sPropKey, sPropValue); + } - FailoverInfo fo = null; - String databaseNameProperty = SQLServerDriverStringProperty.DATABASE_NAME.toString(); - String serverNameProperty = SQLServerDriverStringProperty.SERVER_NAME.toString(); - String failOverPartnerProperty = SQLServerDriverStringProperty.FAILOVER_PARTNER.toString(); - String failOverPartnerPropertyValue = activeConnectionProperties.getProperty(failOverPartnerProperty); + FailoverInfo fo = null; + String databaseNameProperty = SQLServerDriverStringProperty.DATABASE_NAME.toString(); + String serverNameProperty = SQLServerDriverStringProperty.SERVER_NAME.toString(); + String failOverPartnerProperty = SQLServerDriverStringProperty.FAILOVER_PARTNER.toString(); + String failOverPartnerPropertyValue = activeConnectionProperties.getProperty(failOverPartnerProperty); - // failoverPartner and multiSubnetFailover=true cannot be used together - if (multiSubnetFailover && failOverPartnerPropertyValue != null) { - SQLServerException.makeFromDriverError(this, this, - SQLServerException.getErrString("R_dbMirroringWithMultiSubnetFailover"), null, false); - } + // failoverPartner and multiSubnetFailover=true cannot be used together + if (multiSubnetFailover && failOverPartnerPropertyValue != null) { + SQLServerException.makeFromDriverError(this, this, + SQLServerException.getErrString("R_dbMirroringWithMultiSubnetFailover"), null, false); + } - // transparentNetworkIPResolution is ignored if multiSubnetFailover or DBMirroring is true and user did - // not - // set TNIR explicitly - if ((multiSubnetFailover || null != failOverPartnerPropertyValue) && !userSetTNIR) { - transparentNetworkIPResolution = false; - } + // transparentNetworkIPResolution is ignored if multiSubnetFailover or DBMirroring is true and user did not + // set TNIR explicitly + if ((multiSubnetFailover || null != failOverPartnerPropertyValue) && !userSetTNIR) { + transparentNetworkIPResolution = false; + } - // failoverPartner and applicationIntent=ReadOnly cannot be used together - if ((applicationIntent != null) && applicationIntent.equals(ApplicationIntent.READ_ONLY) - && failOverPartnerPropertyValue != null) { - SQLServerException.makeFromDriverError(this, this, - SQLServerException.getErrString("R_dbMirroringWithReadOnlyIntent"), null, false); - } + // failoverPartner and applicationIntent=ReadOnly cannot be used together + if ((applicationIntent != null) && applicationIntent.equals(ApplicationIntent.READ_ONLY) + && failOverPartnerPropertyValue != null) { + SQLServerException.makeFromDriverError(this, this, + SQLServerException.getErrString("R_dbMirroringWithReadOnlyIntent"), null, false); + } - // check to see failover specified without DB error here if not. - if (null != activeConnectionProperties.getProperty(databaseNameProperty)) { - // look to see if there exists a failover - fo = FailoverMapSingleton.getFailoverInfo(this, - activeConnectionProperties.getProperty(serverNameProperty), - activeConnectionProperties.getProperty(instanceNameProperty), - activeConnectionProperties.getProperty(databaseNameProperty)); - } else { - // it is an error to specify failover without db. - if (null != failOverPartnerPropertyValue) - SQLServerException.makeFromDriverError(this, this, - SQLServerException.getErrString("R_failoverPartnerWithoutDB"), null, true); - } + // check to see failover specified without DB error here if not. + if (null != activeConnectionProperties.getProperty(databaseNameProperty)) { + // look to see if there exists a failover + fo = FailoverMapSingleton.getFailoverInfo(this, + activeConnectionProperties.getProperty(serverNameProperty), + activeConnectionProperties.getProperty(instanceNameProperty), + activeConnectionProperties.getProperty(databaseNameProperty)); + } else { + // it is an error to specify failover without db. + if (null != failOverPartnerPropertyValue) + SQLServerException.makeFromDriverError(this, this, + SQLServerException.getErrString("R_failoverPartnerWithoutDB"), null, true); + } - String mirror = (null == fo) ? failOverPartnerPropertyValue : null; + String mirror = (null == fo) ? failOverPartnerPropertyValue : null; - long startTime = System.currentTimeMillis(); - login(activeConnectionProperties.getProperty(serverNameProperty), instanceValue, nPort, mirror, fo, - loginTimeoutSeconds, startTime); + long startTime = System.currentTimeMillis(); + login(activeConnectionProperties.getProperty(serverNameProperty), instanceValue, nPort, mirror, fo, + loginTimeoutSeconds, startTime); - // If SSL is to be used for the duration of the connection, then make sure - // that the final negotiated TDS packet size is no larger than the SSL record size. - if (TDS.ENCRYPT_ON == negotiatedEncryptionLevel || TDS.ENCRYPT_REQ == negotiatedEncryptionLevel) { - // IBM (Websphere) security provider uses 8K SSL record size. All others use 16K. - int sslRecordSize = Util.isIBM() ? 8192 : 16384; + // If SSL is to be used for the duration of the connection, then make sure + // that the final negotiated TDS packet size is no larger than the SSL record size. + if (TDS.ENCRYPT_ON == negotiatedEncryptionLevel || TDS.ENCRYPT_REQ == negotiatedEncryptionLevel) { + // IBM (Websphere) security provider uses 8K SSL record size. All others use 16K. + int sslRecordSize = Util.isIBM() ? 8192 : 16384; - if (tdsPacketSize > sslRecordSize) { - if (connectionlogger.isLoggable(Level.FINER)) { - connectionlogger.finer(toString() + " Negotiated tdsPacketSize " + tdsPacketSize - + " is too large for SSL with JRE " + Util.SYSTEM_JRE + " (max size is " - + sslRecordSize + ")"); - } - MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_packetSizeTooBigForSSL")); - Object[] msgArgs = {Integer.toString(sslRecordSize)}; - terminate(SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG, form.format(msgArgs)); + if (tdsPacketSize > sslRecordSize) { + if (connectionlogger.isLoggable(Level.FINER)) { + connectionlogger.finer(toString() + " Negotiated tdsPacketSize " + tdsPacketSize + + " is too large for SSL with JRE " + Util.SYSTEM_JRE + " (max size is " + sslRecordSize + + ")"); } + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_packetSizeTooBigForSSL")); + Object[] msgArgs = {Integer.toString(sslRecordSize)}; + terminate(SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG, form.format(msgArgs)); } + } - state = State.Opened; + state = State.Opened; - if (connectionlogger.isLoggable(Level.FINER)) { - connectionlogger.finer(toString() + " End of connect"); - } + if (connectionlogger.isLoggable(Level.FINER)) { + connectionlogger.finer(toString() + " End of connect"); } } finally { // once we exit the connect function, the connection can be only in one of two From 488837c1c164308a124f94f7a59b219f22c7f0c4 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Fri, 22 Nov 2019 12:18:02 -0800 Subject: [PATCH 07/25] Change hashmap to hashtable --- .../microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java index 336516356..b220f1b2a 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java @@ -25,7 +25,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; +import java.util.Hashtable; import java.util.concurrent.atomic.AtomicInteger; import javax.crypto.KeyAgreement; @@ -256,10 +256,10 @@ long getCounter() { final class EnclaveSessionCache { - HashMap sessionCache; + Hashtable sessionCache; EnclaveSessionCache() { - sessionCache = new HashMap<>(0); + sessionCache = new Hashtable<>(0); } void addEntry(String key, BaseAttestationRequest b, EnclaveSession e) { From 6bdd7bbc83434150299dae17fbfb510ee107e7e8 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Tue, 26 Nov 2019 10:55:03 -0800 Subject: [PATCH 08/25] Some test fixes --- .../jdbc/ISQLServerEnclaveProvider.java | 15 +++- .../sqlserver/jdbc/SQLServerConnection.java | 13 +-- .../sqlserver/jdbc/SQLServerDriver.java | 43 ---------- .../jdbc/SQLServerVSMEnclaveProvider.java | 85 ++++++++++--------- .../sqlserver/jdbc/EnclavePackageTest.java | 4 +- 5 files changed, 63 insertions(+), 97 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java index b220f1b2a..ef65b53de 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java @@ -103,7 +103,7 @@ byte[] getBytes() { }; byte[] createSessionSecret(byte[] serverResponse) throws GeneralSecurityException, SQLServerException { - if (serverResponse.length != ENCLAVE_LENGTH) { + if (serverResponse == null || serverResponse.length != ENCLAVE_LENGTH) { SQLServerException.makeFromDriverError(null, this, SQLServerResource.getResource("R_MalformedECDHPublicKey"), "0", false); } @@ -262,8 +262,9 @@ final class EnclaveSessionCache { sessionCache = new Hashtable<>(0); } - void addEntry(String key, BaseAttestationRequest b, EnclaveSession e) { - sessionCache.put(key, new EnclaveCacheEntry(b, e)); + void addEntry(String servername, String attestationUrl, BaseAttestationRequest b, EnclaveSession e) { + System.out.println("Adding session " + e.getSessionID().toString() + " with key: " + servername + attestationUrl); + sessionCache.put(servername+attestationUrl, new EnclaveCacheEntry(b, e,attestationUrl)); } EnclaveCacheEntry getSession(String key) { @@ -282,11 +283,13 @@ class EnclaveCacheEntry { private BaseAttestationRequest bar; private EnclaveSession es; + private String attestationUrl; private long timeCreatedInMillis; - EnclaveCacheEntry(BaseAttestationRequest b, EnclaveSession e) { + EnclaveCacheEntry(BaseAttestationRequest b, EnclaveSession e, String url) { bar = b; es = e; + attestationUrl = url; timeCreatedInMillis = Instant.now().getEpochSecond(); } @@ -301,4 +304,8 @@ BaseAttestationRequest getBaseAttestationRequest() { EnclaveSession getEnclaveSession() { return es; } + + String getAttestationURL() { + return attestationUrl; + } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 2597e84d3..e39b1c50d 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -4651,12 +4651,6 @@ private void onFeatureExtAck(byte featureId, byte[] data) throws SQLServerExcept serverColumnEncryptionVersion = ColumnEncryptionVersion.AE_v2; enclaveType = new String(data, 2, data.length - 2, UTF_16LE); } - - if (!EnclaveType.isValidEnclaveType(enclaveType)) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_enclaveTypeInvalid")); - Object[] msgArgs = {enclaveType}; - throw new SQLServerException(null, form.format(msgArgs), null, 0, false); - } } break; @@ -6484,11 +6478,12 @@ boolean isAEv2() { ArrayList initEnclaveParameters(String userSql, String preparedTypeDefinitions, Parameter[] params, ArrayList parameterNames) throws SQLServerException { if (!this.enclaveEstablished()) { - EnclaveCacheEntry entry = enclaveCache.getSession(this.getEnclaveCacheHash()); + EnclaveCacheEntry entry = enclaveCache.getSession(this.getServerName() + enclaveAttestationUrl); if (null == entry) { enclaveProvider.getAttestationParameters(this.enclaveAttestationUrl); } else { enclaveProvider.setEnclaveSession(entry); + System.out.println("Cache hit, using session: " + entry.getEnclaveSession().getSessionID().toString()); } } return enclaveProvider.createEnclaveSession(this, userSql, preparedTypeDefinitions, params, parameterNames); @@ -6502,8 +6497,8 @@ byte[] generateEnclavePackage(String userSQL, ArrayList enclaveCEKs) thr return (enclaveCEKs.size() > 0) ? enclaveProvider.getEnclavePackage(userSQL, enclaveCEKs) : null; } - String getEnclaveCacheHash() { - return this.hostName + this.enclaveAttestationUrl; + String getServerName() { + return this.trustedServerNameAE; } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java index c665f1c3d..a1a8333b0 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java @@ -112,49 +112,6 @@ static ColumnEncryptionSetting valueOfString(String value) throws SQLServerExcep } } - -enum AttestationProtocol { - HGS("HGS"), - AAS("AAS"); - - private final String protocol; - - AttestationProtocol(String protocol) { - this.protocol = protocol; - } - - static boolean isValidAttestationProtocol(String protocol) { - for (AttestationProtocol p : AttestationProtocol.values()) { - if (protocol.equalsIgnoreCase(p.toString())) { - return true; - } - } - return false; - } -} - - -enum EnclaveType { - VBS("VBS"), - SGX("SGX"); - - private final String type; - - EnclaveType(String type) { - this.type = type; - } - - static boolean isValidEnclaveType(String type) { - for (EnclaveType t : EnclaveType.values()) { - if (type.equalsIgnoreCase(t.toString())) { - return true; - } - } - return false; - } -} - - enum SSLProtocol { TLS("TLS"), TLS_V10("TLSv1"), diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java index 4d23cf4d8..5014d3b57 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java @@ -63,7 +63,7 @@ public ArrayList createEnclaveSession(SQLServerConnection connection, St try { enclaveSession = new EnclaveSession(hgsResponse.getSessionID(), vsmParams.createSessionSecret(hgsResponse.getDHpublicKey())); - SQLServerConnection.enclaveCache.addEntry(connection.getEnclaveCacheHash(), vsmParams, enclaveSession); + SQLServerConnection.enclaveCache.addEntry(connection.getServerName(), connection.enclaveAttestationUrl, vsmParams, enclaveSession); } catch (GeneralSecurityException e) { SQLServerException.makeFromDriverError(connection, this, e.getLocalizedMessage(), "0", false); } @@ -299,6 +299,7 @@ private ArrayList describeParameterEncryption(SQLServerConnection connec public void setEnclaveSession(EnclaveCacheEntry entry) { this.enclaveSession = entry.getEnclaveSession(); this.vsmParams = (VSMAttestationParameters) entry.getBaseAttestationRequest(); + this.attestationURL = entry.getAttestationURL(); } } @@ -350,32 +351,34 @@ class VSMAttestationResponse extends BaseAttestationResponse { * DH Public Key - DHPKsize bytes * DH Public Key Signature - DHPKSsize bytes */ - ByteBuffer response = ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN); - this.totalSize = response.getInt(); - this.identitySize = response.getInt(); - int healthReportSize = response.getInt(); - int enclaveReportSize = response.getInt(); - - enclavePK = new byte[identitySize]; - healthReportCertificate = new byte[healthReportSize]; - enclaveReportPackage = new byte[enclaveReportSize]; - - response.get(enclavePK, 0, identitySize); - response.get(healthReportCertificate, 0, healthReportSize); - response.get(enclaveReportPackage, 0, enclaveReportSize); - - this.sessionInfoSize = response.getInt(); - response.get(sessionID, 0, 8); - this.DHPKsize = response.getInt(); - this.DHPKSsize = response.getInt(); - - DHpublicKey = new byte[DHPKsize]; - publicKeySig = new byte[DHPKSsize]; - - response.get(DHpublicKey, 0, DHPKsize); - response.get(publicKeySig, 0, DHPKSsize); + ByteBuffer response = (null != b) ? ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN) : null; + if (null != response) { + this.totalSize = response.getInt(); + this.identitySize = response.getInt(); + int healthReportSize = response.getInt(); + int enclaveReportSize = response.getInt(); + + enclavePK = new byte[identitySize]; + healthReportCertificate = new byte[healthReportSize]; + enclaveReportPackage = new byte[enclaveReportSize]; + + response.get(enclavePK, 0, identitySize); + response.get(healthReportCertificate, 0, healthReportSize); + response.get(enclaveReportPackage, 0, enclaveReportSize); + + this.sessionInfoSize = response.getInt(); + response.get(sessionID, 0, 8); + this.DHPKsize = response.getInt(); + this.DHPKSsize = response.getInt(); + + DHpublicKey = new byte[DHPKsize]; + publicKeySig = new byte[DHPKSsize]; + + response.get(DHpublicKey, 0, DHPKsize); + response.get(publicKeySig, 0, DHPKSsize); + } - if (0 != response.remaining()) { + if (null == response || 0 != response.remaining()) { SQLServerException.makeFromDriverError(null, this, SQLServerResource.getResource("R_EnclaveResponseLengthError"), "0", false); } @@ -384,26 +387,30 @@ class VSMAttestationResponse extends BaseAttestationResponse { CertificateFactory cf = CertificateFactory.getInstance("X.509"); healthCert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(healthReportCertificate)); } catch (CertificateException ce) { - SQLServerException.makeFromDriverError(null, this, ce.getLocalizedMessage(), "0", false); + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_HealthCertError")); + Object[] msgArgs = {ce.getLocalizedMessage()}; + SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, true); } } @SuppressWarnings("unchecked") void validateCert(byte[] b) throws SQLServerException { - try { - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - Collection certs = (Collection) cf - .generateCertificates(new ByteArrayInputStream(b)); - for (X509Certificate cert : certs) { - try { - healthCert.verify(cert.getPublicKey()); - return; - } catch (SignatureException e) { - // Doesn't match, but continue looping through the rest of the certificates + if (null != b) { + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Collection certs = (Collection) cf + .generateCertificates(new ByteArrayInputStream(b)); + for (X509Certificate cert : certs) { + try { + healthCert.verify(cert.getPublicKey()); + return; + } catch (SignatureException e) { + // Doesn't match, but continue looping through the rest of the certificates + } } + } catch (GeneralSecurityException e) { + SQLServerException.makeFromDriverError(null, this, e.getLocalizedMessage(), "0", false); } - } catch (GeneralSecurityException e) { - SQLServerException.makeFromDriverError(null, this, e.getLocalizedMessage(), "0", false); } SQLServerException.makeFromDriverError(null, this, SQLServerResource.getResource("R_InvalidHealthCert"), "0", false); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java index 1fbaa14c0..de0174d9f 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java @@ -198,7 +198,7 @@ public static void setupEnclave() throws Exception { String enclaveAttestationProtocol = TestUtils.getConfiguredProperty("enclaveAttestationProtocol"); connectionStringEnclave = TestUtils.addOrOverrideProperty(connectionStringEnclave, "enclaveAttestationProtocol", - (null != enclaveAttestationProtocol) ? enclaveAttestationProtocol : AttestationProtocol.HGS.toString()); + (null != enclaveAttestationProtocol) ? enclaveAttestationProtocol : "HGS"); // reset logging to avoid severe logs due to negative testing LogManager.getLogManager().reset(); @@ -257,7 +257,7 @@ public static void testInvalidProperties() { // bad enclaveAttestationProtocol testInvalidProperties( TestUtils.addOrOverrideProperty(connectionStringEnclave, "enclaveAttestationProtocol", ""), - "R_enclaveInvalidAttestationProtocol"); + "R_enclavePropertiesError"); } /* From 0b4085ab29bc0a2c433e565c3b4133533661b35b Mon Sep 17 00:00:00 2001 From: rene-ye Date: Wed, 27 Nov 2019 12:02:08 -0800 Subject: [PATCH 09/25] Added VSM cache --- .../jdbc/ISQLServerEnclaveProvider.java | 13 ++------ .../jdbc/SQLServerAASEnclaveProvider.java | 8 +---- .../sqlserver/jdbc/SQLServerConnection.java | 9 +----- .../jdbc/SQLServerVSMEnclaveProvider.java | 27 +++++++++++----- .../JDBCEncryptionDecryptionTest.java | 32 +++++++++---------- 5 files changed, 38 insertions(+), 51 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java index ef65b53de..7b909acba 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java @@ -83,8 +83,6 @@ ArrayList createEnclaveSession(SQLServerConnection connection, String us * @return the enclave session */ EnclaveSession getEnclaveSession(); - - void setEnclaveSession(EnclaveCacheEntry entry); } @@ -263,8 +261,7 @@ final class EnclaveSessionCache { } void addEntry(String servername, String attestationUrl, BaseAttestationRequest b, EnclaveSession e) { - System.out.println("Adding session " + e.getSessionID().toString() + " with key: " + servername + attestationUrl); - sessionCache.put(servername+attestationUrl, new EnclaveCacheEntry(b, e,attestationUrl)); + sessionCache.put(servername+attestationUrl, new EnclaveCacheEntry(b, e)); } EnclaveCacheEntry getSession(String key) { @@ -283,13 +280,11 @@ class EnclaveCacheEntry { private BaseAttestationRequest bar; private EnclaveSession es; - private String attestationUrl; private long timeCreatedInMillis; - EnclaveCacheEntry(BaseAttestationRequest b, EnclaveSession e, String url) { + EnclaveCacheEntry(BaseAttestationRequest b, EnclaveSession e) { bar = b; es = e; - attestationUrl = url; timeCreatedInMillis = Instant.now().getEpochSecond(); } @@ -304,8 +299,4 @@ BaseAttestationRequest getBaseAttestationRequest() { EnclaveSession getEnclaveSession() { return es; } - - String getAttestationURL() { - return attestationUrl; - } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java index ebfe28651..4cb3627dd 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java @@ -73,7 +73,7 @@ public ArrayList createEnclaveSession(SQLServerConnection connection, St try { enclaveSession = new EnclaveSession(hgsResponse.getSessionID(), aasParams.createSessionSecret(hgsResponse.getDHpublicKey())); - SQLServerConnection.enclaveCache.addEntry(connection.getEnclaveCacheHash(), aasParams, enclaveSession); +// SQLServerConnection.enclaveCache.addEntry(connection.getServerName(), aasParams, enclaveSession); } catch (GeneralSecurityException e) { SQLServerException.makeFromDriverError(connection, this, e.getLocalizedMessage(), "0", false); } @@ -289,12 +289,6 @@ private ArrayList describeParameterEncryption(SQLServerConnection connec } return enclaveRequestedCEKs; } - - @Override - public void setEnclaveSession(EnclaveCacheEntry entry) { - this.enclaveSession = entry.getEnclaveSession(); - this.aasParams = (AASAttestationParameters) entry.getBaseAttestationRequest(); - } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index e39b1c50d..28703dc60 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -6473,19 +6473,12 @@ boolean isAEv2() { } ISQLServerEnclaveProvider enclaveProvider; - static EnclaveSessionCache enclaveCache = new EnclaveSessionCache(); ArrayList initEnclaveParameters(String userSql, String preparedTypeDefinitions, Parameter[] params, ArrayList parameterNames) throws SQLServerException { if (!this.enclaveEstablished()) { - EnclaveCacheEntry entry = enclaveCache.getSession(this.getServerName() + enclaveAttestationUrl); - if (null == entry) { enclaveProvider.getAttestationParameters(this.enclaveAttestationUrl); - } else { - enclaveProvider.setEnclaveSession(entry); - System.out.println("Cache hit, using session: " + entry.getEnclaveSession().getSessionID().toString()); - } - } + }; return enclaveProvider.createEnclaveSession(this, userSql, preparedTypeDefinitions, params, parameterNames); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java index 5014d3b57..593ed69be 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java @@ -40,6 +40,9 @@ */ public class SQLServerVSMEnclaveProvider implements ISQLServerEnclaveProvider { + private static EnclaveSessionCache enclaveCache = new EnclaveSessionCache(); + private static ArrayList invalidatedSessions = new ArrayList<>(); + private VSMAttestationParameters vsmParams = null; private VSMAttestationResponse hgsResponse = null; private String attestationURL = null; @@ -60,10 +63,22 @@ public ArrayList createEnclaveSession(SQLServerConnection connection, St ArrayList b = describeParameterEncryption(connection, userSql, preparedTypeDefinitions, params, parameterNames); if (null != hgsResponse && !connection.enclaveEstablished()) { + // Check if the session exists in our cache + EnclaveCacheEntry entry = enclaveCache.getSession(connection.getServerName() + attestationURL); + if (null != entry) { + EnclaveSession es = entry.getEnclaveSession(); + if (!invalidatedSessions.contains(es.getSessionID())) { + this.enclaveSession = entry.getEnclaveSession(); + this.vsmParams = (VSMAttestationParameters) entry.getBaseAttestationRequest(); + return b; + } + } + // If not, set it up try { enclaveSession = new EnclaveSession(hgsResponse.getSessionID(), vsmParams.createSessionSecret(hgsResponse.getDHpublicKey())); - SQLServerConnection.enclaveCache.addEntry(connection.getServerName(), connection.enclaveAttestationUrl, vsmParams, enclaveSession); + enclaveCache.addEntry(connection.getServerName(), connection.enclaveAttestationUrl, vsmParams, + enclaveSession); } catch (GeneralSecurityException e) { SQLServerException.makeFromDriverError(connection, this, e.getLocalizedMessage(), "0", false); } @@ -73,6 +88,9 @@ public ArrayList createEnclaveSession(SQLServerConnection connection, St @Override public void invalidateEnclaveSession() { + if (null != enclaveSession) { + invalidatedSessions.add(enclaveSession.getSessionID()); + } enclaveSession = null; vsmParams = null; attestationURL = null; @@ -294,13 +312,6 @@ private ArrayList describeParameterEncryption(SQLServerConnection connec } return enclaveRequestedCEKs; } - - @Override - public void setEnclaveSession(EnclaveCacheEntry entry) { - this.enclaveSession = entry.getEnclaveSession(); - this.vsmParams = (VSMAttestationParameters) entry.getBaseAttestationRequest(); - this.attestationURL = entry.getAttestationURL(); - } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java index 2a75721ab..797b4aa59 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java @@ -1233,23 +1233,21 @@ void testWithSpecifiedtype(SQLServerResultSet rs, int numberOfColumns, String[] private void testAlterColumnEncryption(SQLServerStatement stmt, String tableName, String table[][], String cekName) throws SQLException { try (SQLServerConnection con = PrepUtil.getConnection(AETestConnectionString, AEInfo)) { - for (int i = 0; i < table.length; i++) { - // alter deterministic to randomized - String sql = "ALTER TABLE " + AbstractSQLGenerator.escapeIdentifier(tableName) + " ALTER COLUMN " - + ColumnType.DETERMINISTIC.name() + table[i][0] + " " + table[i][1] - + String.format(encryptSql, ColumnType.RANDOMIZED.name(), cekName) + ")"; - try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) TestUtils.getPreparedStmt(con, sql, - stmtColEncSetting)) { - stmt.execute(sql); - if (!TestUtils.isAEv2(con)) { - fail(TestResource.getResource("R_expectedExceptionNotThrown")); - } - } catch (SQLException e) { - if (!TestUtils.isAEv2(con)) { - fail(TestResource.getResource("R_expectedExceptionNotThrown")); - } else { - fail(TestResource.getResource("R_AlterAEv2Error") + e.getMessage() + "Query: " + sql); - } + // alter deterministic to randomized + String sql = "ALTER TABLE " + AbstractSQLGenerator.escapeIdentifier(tableName) + " ALTER COLUMN " + + ColumnType.DETERMINISTIC.name() + table[1][0] + " " + table[1][1] + + String.format(encryptSql, ColumnType.RANDOMIZED.name(), cekName) + ")"; + try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) TestUtils.getPreparedStmt(con, sql, + stmtColEncSetting)) { + stmt.execute(sql); + if (!TestUtils.isAEv2(con)) { + fail(TestResource.getResource("R_expectedExceptionNotThrown")); + } + } catch (SQLException e) { + if (!TestUtils.isAEv2(con)) { + fail(TestResource.getResource("R_expectedExceptionNotThrown")); + } else { + fail(TestResource.getResource("R_AlterAEv2Error") + e.getMessage() + "Query: " + sql); } } } From b0057a2bfc597ac864ef53563edc1786ea2feffa Mon Sep 17 00:00:00 2001 From: rene-ye Date: Wed, 27 Nov 2019 12:18:49 -0800 Subject: [PATCH 10/25] AAS cache --- .../jdbc/SQLServerAASEnclaveProvider.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java index 4cb3627dd..fbf113f22 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java @@ -45,6 +45,9 @@ */ public class SQLServerAASEnclaveProvider implements ISQLServerEnclaveProvider { + private static EnclaveSessionCache enclaveCache = new EnclaveSessionCache(); + private static ArrayList invalidatedSessions = new ArrayList<>(); + private AASAttestationParameters aasParams = null; private AASAttestationResponse hgsResponse = null; private String attestationURL = null; @@ -57,8 +60,7 @@ public void getAttestationParameters(String url) throws SQLServerException { try { aasParams = new AASAttestationParameters(attestationURL); } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + SQLServerException.makeFromDriverError(null, this, e.getLocalizedMessage(), "0", false); } } } @@ -70,10 +72,21 @@ public ArrayList createEnclaveSession(SQLServerConnection connection, St ArrayList b = describeParameterEncryption(connection, userSql, preparedTypeDefinitions, params, parameterNames); if (null != hgsResponse && !connection.enclaveEstablished()) { + // Check if the session exists in our cache + EnclaveCacheEntry entry = enclaveCache.getSession(connection.getServerName() + attestationURL); + if (null != entry) { + EnclaveSession es = entry.getEnclaveSession(); + if (!invalidatedSessions.contains(es.getSessionID())) { + this.enclaveSession = entry.getEnclaveSession(); + this.aasParams = (AASAttestationParameters) entry.getBaseAttestationRequest(); + return b; + } + } try { enclaveSession = new EnclaveSession(hgsResponse.getSessionID(), aasParams.createSessionSecret(hgsResponse.getDHpublicKey())); -// SQLServerConnection.enclaveCache.addEntry(connection.getServerName(), aasParams, enclaveSession); + enclaveCache.addEntry(connection.getServerName(), connection.enclaveAttestationUrl, aasParams, + enclaveSession); } catch (GeneralSecurityException e) { SQLServerException.makeFromDriverError(connection, this, e.getLocalizedMessage(), "0", false); } @@ -83,6 +96,9 @@ public ArrayList createEnclaveSession(SQLServerConnection connection, St @Override public void invalidateEnclaveSession() { + if (null != enclaveSession) { + invalidatedSessions.add(enclaveSession.getSessionID()); + } enclaveSession = null; aasParams = null; attestationURL = null; From f1ba74c1beaa18ef80571cb0aa2d722bfc7f8466 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Wed, 27 Nov 2019 13:44:39 -0800 Subject: [PATCH 11/25] debug lines --- .../jdbc/ISQLServerEnclaveProvider.java | 37 ++++++++++++++++++- .../jdbc/SQLServerAASEnclaveProvider.java | 6 +-- .../jdbc/SQLServerVSMEnclaveProvider.java | 4 +- .../JDBCEncryptionDecryptionTest.java | 29 ++++++++------- 4 files changed, 54 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java index 7b909acba..fcb0b0ba5 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java @@ -13,8 +13,10 @@ import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.SecureRandom; import java.security.Signature; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; @@ -26,6 +28,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Hashtable; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import javax.crypto.KeyAgreement; @@ -232,11 +235,13 @@ class EnclaveSession { private byte[] sessionID; private AtomicInteger counter; private byte[] sessionSecret; + private List uuids; EnclaveSession(byte[] cs, byte[] b) { sessionID = cs; sessionSecret = b; counter = new AtomicInteger(0); + uuids = new ArrayList<>(); } byte[] getSessionID() { @@ -247,9 +252,18 @@ byte[] getSessionSecret() { return sessionSecret; } - long getCounter() { + synchronized long getCounter() { + System.out.println("Current counter: " + counter); return counter.getAndIncrement(); } + + void addUuid(byte[] b) { + uuids.add(b); + } + + List getUsedIds() { + return uuids; + } } @@ -261,7 +275,7 @@ final class EnclaveSessionCache { } void addEntry(String servername, String attestationUrl, BaseAttestationRequest b, EnclaveSession e) { - sessionCache.put(servername+attestationUrl, new EnclaveCacheEntry(b, e)); + sessionCache.put(servername + attestationUrl, new EnclaveCacheEntry(b, e)); } EnclaveCacheEntry getSession(String key) { @@ -300,3 +314,22 @@ EnclaveSession getEnclaveSession() { return es; } } + + +class EnclaveProviderHelpers { + + private static boolean compareBytesInList(List l, byte[] bytes) { + return l.stream().anyMatch(i -> Arrays.equals(i, bytes)); + } + + static synchronized byte[] generateUniqueID(EnclaveSession e) throws NoSuchAlgorithmException { + byte[] randomGUID = null; + do { + randomGUID = new byte[16]; + SecureRandom.getInstanceStrong().nextBytes(randomGUID); + } while (compareBytesInList(e.getUsedIds(), randomGUID)); + e.addUuid(randomGUID); + System.out.println("UUID generated for session " + e.getSessionID() + ": " + randomGUID); + return randomGUID; + } +} diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java index fbf113f22..174f425f0 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java @@ -16,6 +16,7 @@ import java.nio.ByteOrder; import java.security.GeneralSecurityException; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.Signature; import java.security.cert.CertificateFactory; @@ -27,6 +28,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.apache.commons.codec.binary.Base64; @@ -116,9 +118,7 @@ public byte[] getEnclavePackage(String userSQL, ArrayList enclaveCEKs) t ByteArrayOutputStream enclavePackage = new ByteArrayOutputStream(); enclavePackage.writeBytes(enclaveSession.getSessionID()); ByteArrayOutputStream keys = new ByteArrayOutputStream(); - byte[] randomGUID = new byte[16]; - SecureRandom.getInstanceStrong().nextBytes(randomGUID); - keys.writeBytes(randomGUID); + keys.writeBytes(EnclaveProviderHelpers.generateUniqueID(this.enclaveSession)); keys.writeBytes(ByteBuffer.allocate(8).putLong(enclaveSession.getCounter()).array()); keys.writeBytes(MessageDigest.getInstance("SHA-256").digest((userSQL).getBytes(UTF_16LE))); for (byte[] b : enclaveCEKs) { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java index 593ed69be..33c28f507 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java @@ -108,9 +108,7 @@ public byte[] getEnclavePackage(String userSQL, ArrayList enclaveCEKs) t ByteArrayOutputStream enclavePackage = new ByteArrayOutputStream(); enclavePackage.writeBytes(enclaveSession.getSessionID()); ByteArrayOutputStream keys = new ByteArrayOutputStream(); - byte[] randomGUID = new byte[16]; - SecureRandom.getInstanceStrong().nextBytes(randomGUID); - keys.writeBytes(randomGUID); + keys.writeBytes(EnclaveProviderHelpers.generateUniqueID(this.enclaveSession)); keys.writeBytes(ByteBuffer.allocate(8).putLong(enclaveSession.getCounter()).array()); keys.writeBytes(MessageDigest.getInstance("SHA-256").digest((userSQL).getBytes(UTF_16LE))); for (byte[] b : enclaveCEKs) { diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java index 797b4aa59..2f13aa115 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java @@ -1234,20 +1234,21 @@ private void testAlterColumnEncryption(SQLServerStatement stmt, String tableName String cekName) throws SQLException { try (SQLServerConnection con = PrepUtil.getConnection(AETestConnectionString, AEInfo)) { // alter deterministic to randomized - String sql = "ALTER TABLE " + AbstractSQLGenerator.escapeIdentifier(tableName) + " ALTER COLUMN " - + ColumnType.DETERMINISTIC.name() + table[1][0] + " " + table[1][1] - + String.format(encryptSql, ColumnType.RANDOMIZED.name(), cekName) + ")"; - try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) TestUtils.getPreparedStmt(con, sql, - stmtColEncSetting)) { - stmt.execute(sql); - if (!TestUtils.isAEv2(con)) { - fail(TestResource.getResource("R_expectedExceptionNotThrown")); - } - } catch (SQLException e) { - if (!TestUtils.isAEv2(con)) { - fail(TestResource.getResource("R_expectedExceptionNotThrown")); - } else { - fail(TestResource.getResource("R_AlterAEv2Error") + e.getMessage() + "Query: " + sql); + for (int i = 0; i < table.length; i++) { + String sql = "ALTER TABLE " + AbstractSQLGenerator.escapeIdentifier(tableName) + " ALTER COLUMN " + + ColumnType.DETERMINISTIC.name() + table[i][0] + " " + table[i][1] + + String.format(encryptSql, ColumnType.RANDOMIZED.name(), cekName) + ")"; + try { + stmt.execute(sql); + if (!TestUtils.isAEv2(con)) { + fail(TestResource.getResource("R_expectedExceptionNotThrown")); + } + } catch (SQLException e) { + if (!TestUtils.isAEv2(con)) { + fail(TestResource.getResource("R_expectedExceptionNotThrown")); + } else { + fail(TestResource.getResource("R_AlterAEv2Error") + e.getMessage() + "Query: " + sql); + } } } } From 635da6946b73253c913eed2e89912872b386dcaa Mon Sep 17 00:00:00 2001 From: rene-ye Date: Thu, 28 Nov 2019 10:29:13 -0800 Subject: [PATCH 12/25] Fix counter issues --- .../jdbc/ISQLServerEnclaveProvider.java | 22 +------------------ .../jdbc/SQLServerAASEnclaveProvider.java | 6 +++-- .../jdbc/SQLServerVSMEnclaveProvider.java | 6 +++-- .../JDBCEncryptionDecryptionTest.java | 5 +++-- 4 files changed, 12 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java index fcb0b0ba5..01c7ede61 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java @@ -253,10 +253,9 @@ byte[] getSessionSecret() { } synchronized long getCounter() { - System.out.println("Current counter: " + counter); return counter.getAndIncrement(); } - + void addUuid(byte[] b) { uuids.add(b); } @@ -314,22 +313,3 @@ EnclaveSession getEnclaveSession() { return es; } } - - -class EnclaveProviderHelpers { - - private static boolean compareBytesInList(List l, byte[] bytes) { - return l.stream().anyMatch(i -> Arrays.equals(i, bytes)); - } - - static synchronized byte[] generateUniqueID(EnclaveSession e) throws NoSuchAlgorithmException { - byte[] randomGUID = null; - do { - randomGUID = new byte[16]; - SecureRandom.getInstanceStrong().nextBytes(randomGUID); - } while (compareBytesInList(e.getUsedIds(), randomGUID)); - e.addUuid(randomGUID); - System.out.println("UUID generated for session " + e.getSessionID() + ": " + randomGUID); - return randomGUID; - } -} diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java index 174f425f0..f723efaf3 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java @@ -118,8 +118,10 @@ public byte[] getEnclavePackage(String userSQL, ArrayList enclaveCEKs) t ByteArrayOutputStream enclavePackage = new ByteArrayOutputStream(); enclavePackage.writeBytes(enclaveSession.getSessionID()); ByteArrayOutputStream keys = new ByteArrayOutputStream(); - keys.writeBytes(EnclaveProviderHelpers.generateUniqueID(this.enclaveSession)); - keys.writeBytes(ByteBuffer.allocate(8).putLong(enclaveSession.getCounter()).array()); + byte[] randomGUID = new byte[16]; + SecureRandom.getInstanceStrong().nextBytes(randomGUID); + keys.writeBytes(randomGUID); + keys.writeBytes(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(enclaveSession.getCounter()).array()); keys.writeBytes(MessageDigest.getInstance("SHA-256").digest((userSQL).getBytes(UTF_16LE))); for (byte[] b : enclaveCEKs) { keys.writeBytes(b); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java index 33c28f507..ef22c5bcc 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java @@ -108,8 +108,10 @@ public byte[] getEnclavePackage(String userSQL, ArrayList enclaveCEKs) t ByteArrayOutputStream enclavePackage = new ByteArrayOutputStream(); enclavePackage.writeBytes(enclaveSession.getSessionID()); ByteArrayOutputStream keys = new ByteArrayOutputStream(); - keys.writeBytes(EnclaveProviderHelpers.generateUniqueID(this.enclaveSession)); - keys.writeBytes(ByteBuffer.allocate(8).putLong(enclaveSession.getCounter()).array()); + byte[] randomGUID = new byte[16]; + SecureRandom.getInstanceStrong().nextBytes(randomGUID); + keys.writeBytes(randomGUID); + keys.writeBytes(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(enclaveSession.getCounter()).array()); keys.writeBytes(MessageDigest.getInstance("SHA-256").digest((userSQL).getBytes(UTF_16LE))); for (byte[] b : enclaveCEKs) { keys.writeBytes(b); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java index 2f13aa115..2a75721ab 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java @@ -1233,12 +1233,13 @@ void testWithSpecifiedtype(SQLServerResultSet rs, int numberOfColumns, String[] private void testAlterColumnEncryption(SQLServerStatement stmt, String tableName, String table[][], String cekName) throws SQLException { try (SQLServerConnection con = PrepUtil.getConnection(AETestConnectionString, AEInfo)) { - // alter deterministic to randomized for (int i = 0; i < table.length; i++) { + // alter deterministic to randomized String sql = "ALTER TABLE " + AbstractSQLGenerator.escapeIdentifier(tableName) + " ALTER COLUMN " + ColumnType.DETERMINISTIC.name() + table[i][0] + " " + table[i][1] + String.format(encryptSql, ColumnType.RANDOMIZED.name(), cekName) + ")"; - try { + try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) TestUtils.getPreparedStmt(con, sql, + stmtColEncSetting)) { stmt.execute(sql); if (!TestUtils.isAEv2(con)) { fail(TestResource.getResource("R_expectedExceptionNotThrown")); From f554f848bf9e8adad9eeb32c4c3e84dc2a90407a Mon Sep 17 00:00:00 2001 From: rene-ye Date: Thu, 28 Nov 2019 11:03:05 -0800 Subject: [PATCH 13/25] Fix javadoc --- .../com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java index 01c7ede61..b1ce065a8 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java @@ -45,8 +45,6 @@ public interface ISQLServerEnclaveProvider { /** * Returns the attestation parameters * - * @param createNewParameters - * indicates whether to create new parameters * @param url * attestation url * @throws SQLServerException From f42d9e8d4ecfdd6e99e27c4196ad965b814fad9f Mon Sep 17 00:00:00 2001 From: rene-ye Date: Thu, 28 Nov 2019 11:28:51 -0800 Subject: [PATCH 14/25] Changs to removing table entries --- .../jdbc/ISQLServerEnclaveProvider.java | 23 +++--- .../jdbc/SQLServerAASEnclaveProvider.java | 20 ++--- .../jdbc/SQLServerVSMEnclaveProvider.java | 75 +++++++++++++------ 3 files changed, 72 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java index b1ce065a8..96713fd8c 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java @@ -13,10 +13,8 @@ import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.SecureRandom; import java.security.Signature; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; @@ -28,7 +26,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Hashtable; -import java.util.List; +import java.util.Map.Entry; import java.util.concurrent.atomic.AtomicInteger; import javax.crypto.KeyAgreement; @@ -233,13 +231,11 @@ class EnclaveSession { private byte[] sessionID; private AtomicInteger counter; private byte[] sessionSecret; - private List uuids; EnclaveSession(byte[] cs, byte[] b) { sessionID = cs; sessionSecret = b; counter = new AtomicInteger(0); - uuids = new ArrayList<>(); } byte[] getSessionID() { @@ -253,14 +249,6 @@ byte[] getSessionSecret() { synchronized long getCounter() { return counter.getAndIncrement(); } - - void addUuid(byte[] b) { - uuids.add(b); - } - - List getUsedIds() { - return uuids; - } } @@ -275,6 +263,15 @@ void addEntry(String servername, String attestationUrl, BaseAttestationRequest b sessionCache.put(servername + attestationUrl, new EnclaveCacheEntry(b, e)); } + void removeEntry(EnclaveSession e) { + for (Entry entry : sessionCache.entrySet()) { + EnclaveCacheEntry ece = entry.getValue(); + if (Arrays.equals(ece.getEnclaveSession().getSessionID(), e.getSessionID())) { + sessionCache.remove(entry.getKey()); + } + } + } + EnclaveCacheEntry getSession(String key) { EnclaveCacheEntry e = sessionCache.get(key); if (null != e && e.expired()) { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java index f723efaf3..c49b197fe 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java @@ -16,7 +16,6 @@ import java.nio.ByteOrder; import java.security.GeneralSecurityException; import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.Signature; import java.security.cert.CertificateFactory; @@ -28,7 +27,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; -import java.util.List; +import java.util.Hashtable; import java.util.Map; import org.apache.commons.codec.binary.Base64; @@ -48,7 +47,6 @@ public class SQLServerAASEnclaveProvider implements ISQLServerEnclaveProvider { private static EnclaveSessionCache enclaveCache = new EnclaveSessionCache(); - private static ArrayList invalidatedSessions = new ArrayList<>(); private AASAttestationParameters aasParams = null; private AASAttestationResponse hgsResponse = null; @@ -77,12 +75,9 @@ public ArrayList createEnclaveSession(SQLServerConnection connection, St // Check if the session exists in our cache EnclaveCacheEntry entry = enclaveCache.getSession(connection.getServerName() + attestationURL); if (null != entry) { - EnclaveSession es = entry.getEnclaveSession(); - if (!invalidatedSessions.contains(es.getSessionID())) { - this.enclaveSession = entry.getEnclaveSession(); - this.aasParams = (AASAttestationParameters) entry.getBaseAttestationRequest(); - return b; - } + this.enclaveSession = entry.getEnclaveSession(); + this.aasParams = (AASAttestationParameters) entry.getBaseAttestationRequest(); + return b; } try { enclaveSession = new EnclaveSession(hgsResponse.getSessionID(), @@ -99,7 +94,7 @@ public ArrayList createEnclaveSession(SQLServerConnection connection, St @Override public void invalidateEnclaveSession() { if (null != enclaveSession) { - invalidatedSessions.add(enclaveSession.getSessionID()); + enclaveCache.removeEntry(enclaveSession); } enclaveSession = null; aasParams = null; @@ -121,7 +116,8 @@ public byte[] getEnclavePackage(String userSQL, ArrayList enclaveCEKs) t byte[] randomGUID = new byte[16]; SecureRandom.getInstanceStrong().nextBytes(randomGUID); keys.writeBytes(randomGUID); - keys.writeBytes(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(enclaveSession.getCounter()).array()); + keys.writeBytes(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN) + .putLong(enclaveSession.getCounter()).array()); keys.writeBytes(MessageDigest.getInstance("SHA-256").digest((userSQL).getBytes(UTF_16LE))); for (byte[] b : enclaveCEKs) { keys.writeBytes(b); @@ -376,7 +372,7 @@ JsonArray getCertificates() { class AASAttestationResponse extends BaseAttestationResponse { private byte[] attestationToken; - private static Map certificateCache = new HashMap<>(); + private static Hashtable certificateCache = new Hashtable<>(); AASAttestationResponse(byte[] b) throws SQLServerException { /*- diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java index ef22c5bcc..b542f82a7 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java @@ -26,9 +26,11 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.text.MessageFormat; +import java.time.Instant; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.Hashtable; import java.util.Map; @@ -41,17 +43,16 @@ public class SQLServerVSMEnclaveProvider implements ISQLServerEnclaveProvider { private static EnclaveSessionCache enclaveCache = new EnclaveSessionCache(); - private static ArrayList invalidatedSessions = new ArrayList<>(); private VSMAttestationParameters vsmParams = null; private VSMAttestationResponse hgsResponse = null; - private String attestationURL = null; + private String attestationUrl = null; private EnclaveSession enclaveSession = null; @Override public void getAttestationParameters(String url) throws SQLServerException { if (null == vsmParams) { - attestationURL = url; + attestationUrl = url; vsmParams = new VSMAttestationParameters(); } } @@ -64,14 +65,11 @@ public ArrayList createEnclaveSession(SQLServerConnection connection, St parameterNames); if (null != hgsResponse && !connection.enclaveEstablished()) { // Check if the session exists in our cache - EnclaveCacheEntry entry = enclaveCache.getSession(connection.getServerName() + attestationURL); + EnclaveCacheEntry entry = enclaveCache.getSession(connection.getServerName() + attestationUrl); if (null != entry) { - EnclaveSession es = entry.getEnclaveSession(); - if (!invalidatedSessions.contains(es.getSessionID())) { - this.enclaveSession = entry.getEnclaveSession(); - this.vsmParams = (VSMAttestationParameters) entry.getBaseAttestationRequest(); - return b; - } + this.enclaveSession = entry.getEnclaveSession(); + this.vsmParams = (VSMAttestationParameters) entry.getBaseAttestationRequest(); + return b; } // If not, set it up try { @@ -89,11 +87,11 @@ public ArrayList createEnclaveSession(SQLServerConnection connection, St @Override public void invalidateEnclaveSession() { if (null != enclaveSession) { - invalidatedSessions.add(enclaveSession.getSessionID()); + enclaveCache.removeEntry(enclaveSession); } enclaveSession = null; vsmParams = null; - attestationURL = null; + attestationUrl = null; } @Override @@ -111,7 +109,8 @@ public byte[] getEnclavePackage(String userSQL, ArrayList enclaveCEKs) t byte[] randomGUID = new byte[16]; SecureRandom.getInstanceStrong().nextBytes(randomGUID); keys.writeBytes(randomGUID); - keys.writeBytes(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(enclaveSession.getCounter()).array()); + keys.writeBytes(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN) + .putLong(enclaveSession.getCounter()).array()); keys.writeBytes(MessageDigest.getInstance("SHA-256").digest((userSQL).getBytes(UTF_16LE))); for (byte[] b : enclaveCEKs) { keys.writeBytes(b); @@ -142,15 +141,28 @@ private VSMAttestationResponse validateAttestationResponse(VSMAttestationRespons return ar; } + private static Hashtable certificateCache = new Hashtable<>(); + private byte[] getAttestationCertificates() throws IOException { - java.net.URL url = new java.net.URL(attestationURL + "/attestationservice.svc/v2.0/signingCertificates/"); - java.net.URLConnection con = url.openConnection(); - String s = new String(con.getInputStream().readAllBytes()); - // omit the square brackets that come with the JSON - String[] bytesString = s.substring(1, s.length() - 1).split(","); - byte[] certData = new byte[bytesString.length]; - for (int i = 0; i < certData.length; i++) { - certData[i] = (byte) (Integer.parseInt(bytesString[i])); + byte[] certData = null; + X509CertificateEntry cacheEntry = certificateCache.get(attestationUrl); + if (null != cacheEntry && !cacheEntry.expired()) { + certData = cacheEntry.getCertificates(); + } else if (null != cacheEntry && cacheEntry.expired()) { + certificateCache.remove(attestationUrl); + } + + if (null == certData) { + java.net.URL url = new java.net.URL(attestationUrl + "/attestationservice.svc/v2.0/signingCertificates/"); + java.net.URLConnection con = url.openConnection(); + String s = new String(con.getInputStream().readAllBytes()); + // omit the square brackets that come with the JSON + String[] bytesString = s.substring(1, s.length() - 1).split(","); + certData = new byte[bytesString.length]; + for (int i = 0; i < certData.length; i++) { + certData[i] = (byte) (Integer.parseInt(bytesString[i])); + } + certificateCache.put(attestationUrl, new X509CertificateEntry(certData)); } return certData; } @@ -474,3 +486,24 @@ void validateStatementSignature() throws SQLServerException, GeneralSecurityExce } } } + + +class X509CertificateEntry { + private static final long TWENTY_FOUR_HOUR_IN_MILLIS = 86400000; + + private byte[] certificates; + private long timeCreatedInMillis; + + X509CertificateEntry(byte[] b) { + certificates = b; + timeCreatedInMillis = Instant.now().getEpochSecond(); + } + + boolean expired() { + return (Instant.now().getEpochSecond() - timeCreatedInMillis) > TWENTY_FOUR_HOUR_IN_MILLIS; + } + + byte[] getCertificates() { + return certificates; + } +} From 276f16f1496c0f4d04a6dd5c3e4b6d4d8ecdec50 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Thu, 28 Nov 2019 11:46:15 -0800 Subject: [PATCH 15/25] Fix timeout inaccuracies --- .../jdbc/ISQLServerEnclaveProvider.java | 10 +++++----- .../jdbc/SQLServerAASEnclaveProvider.java | 19 ++++++++++--------- .../sqlserver/jdbc/SQLServerConnection.java | 4 ++-- .../jdbc/SQLServerVSMEnclaveProvider.java | 10 +++++----- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java index 96713fd8c..c41ee0b8f 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java @@ -284,20 +284,20 @@ EnclaveCacheEntry getSession(String key) { class EnclaveCacheEntry { - private static final long EIGHT_HOUR_IN_MILLIS = 28800000; + private static final long EIGHT_HOURS_IN_SECONDS = 28800; private BaseAttestationRequest bar; private EnclaveSession es; - private long timeCreatedInMillis; + private long timeCreatedInSeconds; EnclaveCacheEntry(BaseAttestationRequest b, EnclaveSession e) { bar = b; es = e; - timeCreatedInMillis = Instant.now().getEpochSecond(); + timeCreatedInSeconds = Instant.now().getEpochSecond(); } - public boolean expired() { - return (Instant.now().getEpochSecond() - timeCreatedInMillis) > EIGHT_HOUR_IN_MILLIS; + boolean expired() { + return (Instant.now().getEpochSecond() - timeCreatedInSeconds) > EIGHT_HOURS_IN_SECONDS; } BaseAttestationRequest getBaseAttestationRequest() { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java index c49b197fe..c89d9a8c4 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java @@ -40,7 +40,7 @@ /** * - * Provides the implementation of the VSM Enclave Provider. The enclave provider encapsulates the client-side + * Provides the implementation of the AAS Enclave Provider. The enclave provider encapsulates the client-side * implementation details of the enclave attestation protocol. * */ @@ -194,9 +194,10 @@ private ArrayList describeParameterEncryption(SQLServerConnection connec rs.getInt(DescribeParameterEncryptionResultSet1.KeyVersion.value()), mdVer, keyPath, keyStoreName, algo); - // servers supporting enclave computations should always return a boolean indicating whether the key - // is - // required by enclave or not. + /* + * servers supporting enclave computations should always return a boolean indicating whether the key + * is required by enclave or not. + */ if (ColumnEncryptionVersion.AE_v2.value() <= connection.getServerColumnEncryptionVersion() .value()) { isRequestedByEnclave = rs @@ -247,7 +248,7 @@ private ArrayList describeParameterEncryption(SQLServerConnection connec // cekEntry will be null if none of the parameters are encrypted. if ((null != cekEntry) && (cekList.size() < cekOrdinal)) { MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_InvalidEncryptionKeyOridnal")); + SQLServerException.getErrString("R_InvalidEncryptionKeyOrdinal")); Object[] msgArgs = {cekOrdinal, cekEntry.getSize()}; throw new SQLServerException(this, form.format(msgArgs), null, 0, false); } @@ -348,18 +349,18 @@ byte[] getNonce() { class JWTCertificateEntry { - private static final long TWENTY_FOUR_HOUR_IN_MILLIS = 86400000; + private static final long TWENTY_FOUR_HOUR_IN_SECONDS = 86400; private JsonArray certificates; - private long timeCreatedInMillis; + private long timeCreatedInSeconds; JWTCertificateEntry(JsonArray j) { certificates = j; - timeCreatedInMillis = Instant.now().getEpochSecond(); + timeCreatedInSeconds = Instant.now().getEpochSecond(); } boolean expired() { - return (Instant.now().getEpochSecond() - timeCreatedInMillis) > TWENTY_FOUR_HOUR_IN_MILLIS; + return (Instant.now().getEpochSecond() - timeCreatedInSeconds) > TWENTY_FOUR_HOUR_IN_SECONDS; } JsonArray getCertificates() { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 28703dc60..8139f1a38 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -6477,8 +6477,8 @@ boolean isAEv2() { ArrayList initEnclaveParameters(String userSql, String preparedTypeDefinitions, Parameter[] params, ArrayList parameterNames) throws SQLServerException { if (!this.enclaveEstablished()) { - enclaveProvider.getAttestationParameters(this.enclaveAttestationUrl); - }; + enclaveProvider.getAttestationParameters(this.enclaveAttestationUrl); + } return enclaveProvider.createEnclaveSession(this, userSql, preparedTypeDefinitions, params, parameterNames); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java index b542f82a7..64da87d07 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java @@ -268,7 +268,7 @@ private ArrayList describeParameterEncryption(SQLServerConnection connec // cekEntry will be null if none of the parameters are encrypted. if ((null != cekEntry) && (cekList.size() < cekOrdinal)) { MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_InvalidEncryptionKeyOridnal")); + SQLServerException.getErrString("R_InvalidEncryptionKeyOrdinal")); Object[] msgArgs = {cekOrdinal, cekEntry.getSize()}; throw new SQLServerException(this, form.format(msgArgs), null, 0, false); } @@ -489,18 +489,18 @@ void validateStatementSignature() throws SQLServerException, GeneralSecurityExce class X509CertificateEntry { - private static final long TWENTY_FOUR_HOUR_IN_MILLIS = 86400000; + private static final long EIGHT_HOUR_IN_SECONDS = 28800; private byte[] certificates; - private long timeCreatedInMillis; + private long timeCreatedInSeconds; X509CertificateEntry(byte[] b) { certificates = b; - timeCreatedInMillis = Instant.now().getEpochSecond(); + timeCreatedInSeconds = Instant.now().getEpochSecond(); } boolean expired() { - return (Instant.now().getEpochSecond() - timeCreatedInMillis) > TWENTY_FOUR_HOUR_IN_MILLIS; + return (Instant.now().getEpochSecond() - timeCreatedInSeconds) > EIGHT_HOUR_IN_SECONDS; } byte[] getCertificates() { From cffcd955fe8e6010cc500d9a10371cfb1fd8e998 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Thu, 5 Dec 2019 14:04:33 -0800 Subject: [PATCH 16/25] review comments --- pom.xml | 19 ++++-- .../jdbc/ISQLServerEnclaveProvider.java | 50 +++++++++++--- .../jdbc/SQLServerAASEnclaveProvider.java | 65 +++++-------------- .../sqlserver/jdbc/SQLServerConnection.java | 15 +++-- .../sqlserver/jdbc/SQLServerDriver.java | 57 ++++++++++++++++ .../sqlserver/jdbc/SQLServerResource.java | 3 +- .../jdbc/SQLServerVSMEnclaveProvider.java | 34 ---------- .../sqlserver/jdbc/EnclavePackageTest.java | 13 +++- .../sqlserver/jdbc/TestResource.java | 3 +- 9 files changed, 153 insertions(+), 106 deletions(-) diff --git a/pom.xml b/pom.xml index 64b65ca79..60f9cc9b4 100644 --- a/pom.xml +++ b/pom.xml @@ -49,13 +49,13 @@ xAzureSQLDB - - - - For tests not compatible with Azure SQL Database - - xAzureSQLDW - - - - For tests not compatible with Azure Data Warehouse - xAzureSQLMI - - - - For tests not compatible with Azure SQL Managed Instance - NTLM - - - - - - - For tests using NTLM Authentication mode (excluded by default) - reqExternalSetup - For tests requiring external setup (excluded by default) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Default testing enabled with SQL Server 2019 (SQLv15) --> + NTLM - - - - - - - For tests using NTLM Authentication mode (excluded by + default) reqExternalSetup - For tests requiring external setup (excluded + by default) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - Default testing enabled with SQL Server 2019 (SQLv15) --> xSQLv15, NTLM, reqExternalSetup - + -preview @@ -65,6 +65,7 @@ 6.0.0 5.0.0 4.7.2 + 2.8.6 [1.3.2, 1.4.2] @@ -107,6 +108,14 @@ true + + + com.google.code.gson + gson + ${google.gson.version} + true + + org.osgi diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java index c41ee0b8f..6cdc4cce3 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java @@ -5,6 +5,9 @@ package com.microsoft.sqlserver.jdbc; +import static java.nio.charset.StandardCharsets.UTF_16LE; + +import java.io.ByteArrayOutputStream; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -15,6 +18,7 @@ import java.security.MessageDigest; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.SecureRandom; import java.security.Signature; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; @@ -38,7 +42,35 @@ * */ public interface ISQLServerEnclaveProvider { - byte[] getEnclavePackage(String userSQL, ArrayList enclaveCEKs) throws SQLServerException; + default byte[] getEnclavePackage(String userSQL, ArrayList enclaveCEKs) throws SQLServerException { + EnclaveSession enclaveSession = getEnclaveSession(); + if (null != enclaveSession) { + try { + ByteArrayOutputStream enclavePackage = new ByteArrayOutputStream(); + enclavePackage.writeBytes(enclaveSession.getSessionID()); + ByteArrayOutputStream keys = new ByteArrayOutputStream(); + byte[] randomGUID = new byte[16]; + SecureRandom.getInstanceStrong().nextBytes(randomGUID); + keys.writeBytes(randomGUID); + keys.writeBytes(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN) + .putLong(enclaveSession.getCounter()).array()); + keys.writeBytes(MessageDigest.getInstance("SHA-256").digest((userSQL).getBytes(UTF_16LE))); + for (byte[] b : enclaveCEKs) { + keys.writeBytes(b); + } + enclaveCEKs.clear(); + SQLServerAeadAes256CbcHmac256EncryptionKey encryptedKey = new SQLServerAeadAes256CbcHmac256EncryptionKey( + enclaveSession.getSessionSecret(), SQLServerAeadAes256CbcHmac256Algorithm.algorithmName); + SQLServerAeadAes256CbcHmac256Algorithm algo = new SQLServerAeadAes256CbcHmac256Algorithm(encryptedKey, + SQLServerEncryptionType.Randomized, (byte) 0x1); + enclavePackage.writeBytes(algo.encryptData(keys.toByteArray())); + return enclavePackage.toByteArray(); + } catch (GeneralSecurityException | SQLServerException e) { + SQLServerException.makeFromDriverError(null, this, e.getLocalizedMessage(), "0", false); + } + } + return null; + } /** * Returns the attestation parameters @@ -86,12 +118,11 @@ ArrayList createEnclaveSession(SQLServerConnection connection, String us abstract class BaseAttestationRequest { + protected static final byte[] ECDH_MAGIC = {0x45, 0x43, 0x4b, 0x33, 0x30, 0x00, 0x00, 0x00}; + protected static final int ENCLAVE_LENGTH = 104; + protected PrivateKey privateKey; - // Static byte[] for VSM ECDH - protected static byte ECDH_MAGIC[] = {0x45, 0x43, 0x4b, 0x33, 0x30, 0x00, 0x00, 0x00}; - // VSM doesn't have a challenge - protected byte enclaveChallenge[]; - protected static int ENCLAVE_LENGTH = 104; + protected byte[] enclaveChallenge; protected byte[] x; protected byte[] y; @@ -151,10 +182,10 @@ void initBcryptECDH() throws SQLServerException { * For some reason toByteArray doesn't have an Signum option like the constructor. Manually remove leading 00 * byte if it exists. */ - if (x[0] == 0 && x.length != 48) { + if (0 == x[0] && 48 != x.length) { x = Arrays.copyOfRange(x, 1, x.length); } - if (y[0] == 0 && y.length != 48) { + if (0 == y[0] && 48 != y.length) { y = Arrays.copyOfRange(y, 1, y.length); } } @@ -175,6 +206,7 @@ abstract class BaseAttestationResponse { protected byte[] DHpublicKey; protected byte[] publicKeySig; + @SuppressWarnings("unused") void validateDHPublicKey() throws SQLServerException, GeneralSecurityException { /*- * Java doesn't directly support PKCS1 padding for RSA keys. Parse the key bytes and create a RSAPublicKeySpec @@ -253,7 +285,7 @@ synchronized long getCounter() { final class EnclaveSessionCache { - Hashtable sessionCache; + private Hashtable sessionCache; EnclaveSessionCache() { sessionCache = new Hashtable<>(0); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java index c89d9a8c4..b59a1256e 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java @@ -15,7 +15,6 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.GeneralSecurityException; -import java.security.MessageDigest; import java.security.SecureRandom; import java.security.Signature; import java.security.cert.CertificateFactory; @@ -26,12 +25,11 @@ import java.text.MessageFormat; import java.time.Instant; import java.util.ArrayList; +import java.util.Base64; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; -import org.apache.commons.codec.binary.Base64; - import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -106,36 +104,6 @@ public EnclaveSession getEnclaveSession() { return enclaveSession; } - @Override - public byte[] getEnclavePackage(String userSQL, ArrayList enclaveCEKs) throws SQLServerException { - if (null != enclaveSession) { - try { - ByteArrayOutputStream enclavePackage = new ByteArrayOutputStream(); - enclavePackage.writeBytes(enclaveSession.getSessionID()); - ByteArrayOutputStream keys = new ByteArrayOutputStream(); - byte[] randomGUID = new byte[16]; - SecureRandom.getInstanceStrong().nextBytes(randomGUID); - keys.writeBytes(randomGUID); - keys.writeBytes(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN) - .putLong(enclaveSession.getCounter()).array()); - keys.writeBytes(MessageDigest.getInstance("SHA-256").digest((userSQL).getBytes(UTF_16LE))); - for (byte[] b : enclaveCEKs) { - keys.writeBytes(b); - } - enclaveCEKs.clear(); - SQLServerAeadAes256CbcHmac256EncryptionKey encryptedKey = new SQLServerAeadAes256CbcHmac256EncryptionKey( - enclaveSession.getSessionSecret(), SQLServerAeadAes256CbcHmac256Algorithm.algorithmName); - SQLServerAeadAes256CbcHmac256Algorithm algo = new SQLServerAeadAes256CbcHmac256Algorithm(encryptedKey, - SQLServerEncryptionType.Randomized, (byte) 0x1); - enclavePackage.writeBytes(algo.encryptData(keys.toByteArray())); - return enclavePackage.toByteArray(); - } catch (GeneralSecurityException | SQLServerException e) { - SQLServerException.makeFromDriverError(null, this, e.getLocalizedMessage(), "0", false); - } - } - return null; - } - private AASAttestationResponse validateAttestationResponse(AASAttestationResponse ar) throws SQLServerException { try { ar.validateToken(attestationURL); @@ -310,9 +278,9 @@ private ArrayList describeParameterEncryption(SQLServerConnection connec class AASAttestationParameters extends BaseAttestationRequest { // Type 1 is AAS, sent as Little Endian 0x10000000 - private static byte ENCLAVE_TYPE[] = new byte[] {0x1, 0x0, 0x0, 0x0}; + private static final byte[] ENCLAVE_TYPE = new byte[] {0x1, 0x0, 0x0, 0x0}; // Nonce length is always 256 - private static byte NONCE_LENGTH[] = new byte[] {0x0, 0x1, 0x0, 0x0}; + private static byte[] NONCE_LENGTH = new byte[] {0x0, 0x1, 0x0, 0x0}; private byte[] nonce = new byte[256]; AASAttestationParameters(String attestationUrl) throws SQLServerException, IOException { @@ -421,19 +389,20 @@ class AASAttestationResponse extends BaseAttestationResponse { } void validateToken(String attestationUrl) throws SQLServerException { - /* - * 3 parts of our JWT token: Header, Body, and Signature. Broken up via '.' - */ - String jwtToken = (new String(attestationToken)).trim(); - if (jwtToken.startsWith("\"") && jwtToken.endsWith("\"")) { - jwtToken = jwtToken.substring(1, jwtToken.length() - 1); - } - String[] splitString = jwtToken.split("\\."); - String Header = new String(Base64.decodeBase64(splitString[0])); - String Body = new String(Base64.decodeBase64(splitString[1])); - byte[] stmtSig = Base64.decodeBase64(splitString[2]); - try { + /* + * 3 parts of our JWT token: Header, Body, and Signature. Broken up via '.' + */ + String jwtToken = (new String(attestationToken)).trim(); + if (jwtToken.startsWith("\"") && jwtToken.endsWith("\"")) { + jwtToken = jwtToken.substring(1, jwtToken.length() - 1); + } + String[] splitString = jwtToken.split("\\."); + java.util.Base64.Decoder decoder = Base64.getUrlDecoder(); + String Header = new String(decoder.decode(splitString[0])); + String Body = new String(decoder.decode(splitString[1])); + byte[] stmtSig = decoder.decode(splitString[2]); + JsonArray keys = null; JWTCertificateEntry cacheEntry = certificateCache.get(attestationUrl); if (null != cacheEntry && !cacheEntry.expired()) { @@ -484,7 +453,7 @@ void validateToken(String attestationUrl) throws SQLServerException { } } } - SQLServerException.makeFromDriverError(null, this, SQLServerResource.getResource("TODO: ADD MESSAGE"), "0", + SQLServerException.makeFromDriverError(null, this, SQLServerResource.getResource("R_AasJWTError"), "0", false); } catch (IOException | GeneralSecurityException e) { SQLServerException.makeFromDriverError(null, this, e.getLocalizedMessage(), "", false); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 8139f1a38..4a397bf50 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -1472,15 +1472,16 @@ Connection connectInternal(Properties propsIn, sPropValue = activeConnectionProperties.getProperty(sPropKey); if (null != sPropValue) { enclaveAttestationProtocol = sPropValue; - if (enclaveAttestationProtocol.equalsIgnoreCase("HGS")) { + if (!AttestationProtocol.isValidAttestationProtocol(enclaveAttestationProtocol)) { + throw new SQLServerException(SQLServerException.getErrString("R_enclaveInvalidAttestationProtocol"), + null); + } + + if (enclaveAttestationProtocol.equalsIgnoreCase(AttestationProtocol.HGS.toString())) { this.enclaveProvider = new SQLServerVSMEnclaveProvider(); - } else if (enclaveAttestationProtocol.equalsIgnoreCase("AAS")) { - this.enclaveProvider = new SQLServerAASEnclaveProvider(); } else { - if (connectionlogger.isLoggable(Level.SEVERE)) { - connectionlogger.severe(toString() + " " - + SQLServerException.getErrString("R_enclaveInvalidAttestationProtocol")); - } + // If it's a valid Provider & not HGS, then it has to be AAS + this.enclaveProvider = new SQLServerAASEnclaveProvider(); } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java index a1a8333b0..8ee0047b9 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java @@ -112,6 +112,63 @@ static ColumnEncryptionSetting valueOfString(String value) throws SQLServerExcep } } + +enum AttestationProtocol { + HGS("HGS"), + AAS("AAS"); + + private final String protocol; + + AttestationProtocol(String protocol) { + this.protocol = protocol; + } + + static boolean isValidAttestationProtocol(String protocol) { + for (AttestationProtocol p : AttestationProtocol.values()) { + if (protocol.equalsIgnoreCase(p.toString())) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return protocol; + } +} + + +enum EnclaveType { + VBS("VBS"), + SGX("SGX"); + + private final String type; + + EnclaveType(String type) { + this.type = type; + } + + public int getValue() { + return ordinal() + 1; + } + + static boolean isValidEnclaveType(String type) { + for (EnclaveType t : EnclaveType.values()) { + if (type.equalsIgnoreCase(t.toString())) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return type; + } +} + + enum SSLProtocol { TLS("TLS"), TLS_V10("TLSv1"), diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index cbbbf955d..4d2d711f9 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -618,5 +618,6 @@ protected Object[][] getContents() { {"R_InvalidSignedStatement", " Enclave Attestation failed, the statement bytes were not signed by the health certificate."}, {"R_InvalidDHKeySignature", - "Enclave Attestation failed, the DH Public Key signature can't be verified with the enclave PK."},}; + "Enclave Attestation failed, the DH Public Key signature can't be verified with the enclave PK."}, + {"R_AasJWTError", "An error occured when retrieving and validating the JSON Web Token."}}; }; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java index 64da87d07..8d758cd3d 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java @@ -5,16 +5,12 @@ package com.microsoft.sqlserver.jdbc; -import static java.nio.charset.StandardCharsets.UTF_16LE; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.GeneralSecurityException; -import java.security.MessageDigest; -import java.security.SecureRandom; import java.security.Signature; import java.security.SignatureException; import java.security.cert.CertificateException; @@ -99,36 +95,6 @@ public EnclaveSession getEnclaveSession() { return enclaveSession; } - @Override - public byte[] getEnclavePackage(String userSQL, ArrayList enclaveCEKs) throws SQLServerException { - if (null != enclaveSession) { - try { - ByteArrayOutputStream enclavePackage = new ByteArrayOutputStream(); - enclavePackage.writeBytes(enclaveSession.getSessionID()); - ByteArrayOutputStream keys = new ByteArrayOutputStream(); - byte[] randomGUID = new byte[16]; - SecureRandom.getInstanceStrong().nextBytes(randomGUID); - keys.writeBytes(randomGUID); - keys.writeBytes(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN) - .putLong(enclaveSession.getCounter()).array()); - keys.writeBytes(MessageDigest.getInstance("SHA-256").digest((userSQL).getBytes(UTF_16LE))); - for (byte[] b : enclaveCEKs) { - keys.writeBytes(b); - } - enclaveCEKs.clear(); - SQLServerAeadAes256CbcHmac256EncryptionKey encryptedKey = new SQLServerAeadAes256CbcHmac256EncryptionKey( - enclaveSession.getSessionSecret(), SQLServerAeadAes256CbcHmac256Algorithm.algorithmName); - SQLServerAeadAes256CbcHmac256Algorithm algo = new SQLServerAeadAes256CbcHmac256Algorithm(encryptedKey, - SQLServerEncryptionType.Randomized, (byte) 0x1); - enclavePackage.writeBytes(algo.encryptData(keys.toByteArray())); - return enclavePackage.toByteArray(); - } catch (GeneralSecurityException | SQLServerException e) { - SQLServerException.makeFromDriverError(null, this, e.getLocalizedMessage(), "0", false); - } - } - return null; - } - private VSMAttestationResponse validateAttestationResponse(VSMAttestationResponse ar) throws SQLServerException { try { byte[] attestationCerts = getAttestationCertificates(); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java index de0174d9f..3e266cc03 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java @@ -10,6 +10,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -440,7 +441,17 @@ private static void verifyEnclaveEnabled(Connection con) throws SQLException { try (Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery( "SELECT [name], [value], [value_in_use] FROM sys.configurations WHERE [name] = 'column encryption enclave type';")) { while (rs.next()) { - assertEquals("1", rs.getString(2)); + String enclaveType = rs.getString(2); + String enclaveAttestationProtocol = getConfiguredProperty("enclaveAttestationProtocol"); + if (String.valueOf(AttestationProtocol.HGS).equals(enclaveAttestationProtocol)) { + assertEquals(EnclaveType.VBS.getValue(), Integer.parseInt(enclaveType)); + } else if (String.valueOf(AttestationProtocol.AAS).equals(enclaveAttestationProtocol)) { + assertEquals(EnclaveType.SGX.getValue(), Integer.parseInt(enclaveType)); + } else { + MessageFormat form = new MessageFormat(TestResource.getResource("R_invalidEnclaveType")); + Object[] msgArgs = {enclaveType}; + fail(form.format(msgArgs)); + } } } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java index de1a912ba..80d641d22 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java @@ -182,5 +182,6 @@ protected Object[][] getContents() { {"R_NoPrivilege", "The EXECUTE permission was denied on the object {0}"}, {"R_resultSetEmpty", "Result set is empty."}, {"R_AlterAEv2Error", "Alter Column Encryption failed."}, {"R_RichQueryError", "Rich query failed."}, {"R_reqExternalSetup", "External setup for test required."}, - {"R_invalidEnclaveSessionFailed", "invalidate enclave session failed."}}; + {"R_invalidEnclaveSessionFailed", "invalidate enclave session failed."}, + {"R_invalidEnclaveType", "Invalid enclave type {0}."}}; } From ad2b0eccb7abb73aab35671b6b4b1e4876fe93b7 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Thu, 5 Dec 2019 14:06:20 -0800 Subject: [PATCH 17/25] Pom formatting revert --- pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 60f9cc9b4..8c319b81f 100644 --- a/pom.xml +++ b/pom.xml @@ -49,13 +49,13 @@ xAzureSQLDB - - - - For tests not compatible with Azure SQL Database - - xAzureSQLDW - - - - For tests not compatible with Azure Data Warehouse - xAzureSQLMI - - - - For tests not compatible with Azure SQL Managed Instance - NTLM - - - - - - - For tests using NTLM Authentication mode (excluded by - default) reqExternalSetup - For tests requiring external setup (excluded - by default) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Default testing enabled with SQL Server 2019 (SQLv15) --> + NTLM - - - - - - - For tests using NTLM Authentication mode (excluded by default) + reqExternalSetup - For tests requiring external setup (excluded by default) + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Default testing enabled with SQL Server 2019 (SQLv15) --> xSQLv15, NTLM, reqExternalSetup - + -preview From c53ae671d063e1d7deec154be4555a62f7892a2a Mon Sep 17 00:00:00 2001 From: rene-ye Date: Thu, 5 Dec 2019 14:56:03 -0800 Subject: [PATCH 18/25] some more changes --- .../jdbc/SQLServerAASEnclaveProvider.java | 31 ++++++++++++++----- .../sqlserver/jdbc/SQLServerConnection.java | 6 ++++ .../sqlserver/jdbc/SQLServerResource.java | 4 ++- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java index b59a1256e..7f167d8b1 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java @@ -25,6 +25,7 @@ import java.text.MessageFormat; import java.time.Instant; import java.util.ArrayList; +import java.util.Arrays; import java.util.Base64; import java.util.HashMap; import java.util.Hashtable; @@ -106,7 +107,7 @@ public EnclaveSession getEnclaveSession() { private AASAttestationResponse validateAttestationResponse(AASAttestationResponse ar) throws SQLServerException { try { - ar.validateToken(attestationURL); + ar.validateToken(attestationURL, aasParams.getNonce()); ar.validateDHPublicKey(aasParams.getNonce()); } catch (GeneralSecurityException e) { SQLServerException.makeFromDriverError(null, this, e.getLocalizedMessage(), "0", false); @@ -388,7 +389,7 @@ class AASAttestationResponse extends BaseAttestationResponse { } } - void validateToken(String attestationUrl) throws SQLServerException { + void validateToken(String attestationUrl, byte[] nonce) throws SQLServerException { try { /* * 3 parts of our JWT token: Header, Body, and Signature. Broken up via '.' @@ -399,8 +400,8 @@ void validateToken(String attestationUrl) throws SQLServerException { } String[] splitString = jwtToken.split("\\."); java.util.Base64.Decoder decoder = Base64.getUrlDecoder(); - String Header = new String(decoder.decode(splitString[0])); - String Body = new String(decoder.decode(splitString[1])); + String header = new String(decoder.decode(splitString[0])); + String body = new String(decoder.decode(splitString[1])); byte[] stmtSig = decoder.decode(splitString[2]); JsonArray keys = null; @@ -417,17 +418,18 @@ void validateToken(String attestationUrl) throws SQLServerException { URL wellKnownUrl = new URL("https://" + authorityUrl + "/.well-known/openid-configuration"); URLConnection con = wellKnownUrl.openConnection(); String wellKnownUrlJson = new String(con.getInputStream().readAllBytes()); - JsonObject attestationJson = new JsonParser().parse(wellKnownUrlJson).getAsJsonObject(); + JsonObject attestationJson = JsonParser.parseString(wellKnownUrlJson).getAsJsonObject(); // Get our Keys URL jwksUrl = new URL(attestationJson.get("jwks_uri").getAsString()); URLConnection jwksCon = jwksUrl.openConnection(); String jwksUrlJson = new String(jwksCon.getInputStream().readAllBytes()); - JsonObject jwksJson = new JsonParser().parse(jwksUrlJson).getAsJsonObject(); + JsonObject jwksJson = JsonParser.parseString(jwksUrlJson).getAsJsonObject(); keys = jwksJson.get("keys").getAsJsonArray(); certificateCache.put(attestationUrl, new JWTCertificateEntry(keys)); } // Find the specific keyID we need from our header - JsonObject headerJsonObject = new JsonParser().parse(Header).getAsJsonObject(); + + JsonObject headerJsonObject = JsonParser.parseString(header).getAsJsonObject(); String keyID = headerJsonObject.get("kid").getAsString(); // Iterate through our list of keys and find the one with the same keyID for (JsonElement key : keys) { @@ -448,6 +450,21 @@ void validateToken(String attestationUrl) throws SQLServerException { sig.initVerify(cert.getPublicKey()); sig.update(signatureBytes); if (sig.verify(stmtSig)) { + // Token is verified, now check the aas-ehd + JsonObject bodyJsonObject = JsonParser.parseString(body).getAsJsonObject(); + String aasEhd = bodyJsonObject.get("aas-ehd").getAsString(); + if (!Arrays.equals(Base64.getUrlDecoder().decode(aasEhd), enclavePK)) { + SQLServerException.makeFromDriverError(null, this, + SQLServerResource.getResource("R_AasEhdError"), "0", false); + } + if (this.enclaveType == 1) { + // Verify rp_data claim as well if VBS + String rpData = bodyJsonObject.get("rp_data").getAsString(); + if (!Arrays.equals(Base64.getUrlDecoder().decode(rpData), nonce)) { + SQLServerException.makeFromDriverError(null, this, + SQLServerResource.getResource("R_VbsRpDataError"), "0", false); + } + } return; } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 4a397bf50..e828d5d66 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -4653,6 +4653,12 @@ private void onFeatureExtAck(byte featureId, byte[] data) throws SQLServerExcept enclaveType = new String(data, 2, data.length - 2, UTF_16LE); } } + + if (!EnclaveType.isValidEnclaveType(enclaveType)) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_enclaveTypeInvalid")); + Object[] msgArgs = {enclaveType}; + throw new SQLServerException(null, form.format(msgArgs), null, 0, false); + } break; } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index 4d2d711f9..c73b00902 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -619,5 +619,7 @@ protected Object[][] getContents() { " Enclave Attestation failed, the statement bytes were not signed by the health certificate."}, {"R_InvalidDHKeySignature", "Enclave Attestation failed, the DH Public Key signature can't be verified with the enclave PK."}, - {"R_AasJWTError", "An error occured when retrieving and validating the JSON Web Token."}}; + {"R_AasJWTError", "An error occured when retrieving and validating the JSON Web Token."}, + {"R_AasEhdError", "aas-ehd claim from JWT did not match enclavePK."}, + {"R_VbsRpDataError", "rp_data claim from JWT did not match client nonce."},}; }; From 8fafab09ebb9dfa193d144cd58ea1d42bb40d28b Mon Sep 17 00:00:00 2001 From: rene-ye Date: Thu, 5 Dec 2019 15:37:18 -0800 Subject: [PATCH 19/25] FIx nullptr --- .../microsoft/sqlserver/jdbc/SQLServerConnection.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index e828d5d66..94a80d534 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -4652,12 +4652,12 @@ private void onFeatureExtAck(byte featureId, byte[] data) throws SQLServerExcept serverColumnEncryptionVersion = ColumnEncryptionVersion.AE_v2; enclaveType = new String(data, 2, data.length - 2, UTF_16LE); } - } - if (!EnclaveType.isValidEnclaveType(enclaveType)) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_enclaveTypeInvalid")); - Object[] msgArgs = {enclaveType}; - throw new SQLServerException(null, form.format(msgArgs), null, 0, false); + if (!EnclaveType.isValidEnclaveType(enclaveType)) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_enclaveTypeInvalid")); + Object[] msgArgs = {enclaveType}; + throw new SQLServerException(null, form.format(msgArgs), null, 0, false); + } } break; From 758cee65665aad0c0c2f992dfdaa417b4e780693 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Fri, 6 Dec 2019 09:37:15 -0800 Subject: [PATCH 20/25] fix test --- .../java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java index 3e266cc03..b9c7a5ce8 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java @@ -258,7 +258,7 @@ public static void testInvalidProperties() { // bad enclaveAttestationProtocol testInvalidProperties( TestUtils.addOrOverrideProperty(connectionStringEnclave, "enclaveAttestationProtocol", ""), - "R_enclavePropertiesError"); + "R_enclaveInvalidAttestationProtocol"); } /* From 0d5e693c9c2e5ee6692af7cbb481bac6c0ae4640 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Fri, 6 Dec 2019 10:26:01 -0800 Subject: [PATCH 21/25] debug line for CI --- .../sqlserver/jdbc/SQLServerVSMEnclaveProvider.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java index 8d758cd3d..51f02a21a 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java @@ -282,6 +282,15 @@ private ArrayList describeParameterEncryption(SQLServerConnection connec rs.close(); } catch (SQLException e) { if (e instanceof SQLServerException) { + char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + byte[] bytes = vsmParams.getBytes(); + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + System.out.println("Attestation Params: " + new String(hexChars)); throw (SQLServerException) e; } else { throw new SQLServerException(SQLServerException.getErrString("R_UnableRetrieveParameterMetadata"), null, From 24fe60820eae920d68df4d846116b3e06f76b3e6 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Fri, 6 Dec 2019 11:14:36 -0800 Subject: [PATCH 22/25] more debug --- .../sqlserver/jdbc/ISQLServerEnclaveProvider.java | 2 ++ .../sqlserver/jdbc/SQLServerAASEnclaveProvider.java | 6 ++++++ .../microsoft/sqlserver/jdbc/SQLServerConnection.java | 2 +- .../sqlserver/jdbc/SQLServerVSMEnclaveProvider.java | 5 +++++ .../AlwaysEncrypted/JDBCEncryptionDecryptionTest.java | 10 ++++++++++ 5 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java index 6cdc4cce3..ecaeee571 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java @@ -71,6 +71,8 @@ default byte[] getEnclavePackage(String userSQL, ArrayList enclaveCEKs) } return null; } + + public byte[] getParams(); /** * Returns the attestation parameters diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java index 7f167d8b1..83c24b728 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java @@ -63,6 +63,12 @@ public void getAttestationParameters(String url) throws SQLServerException { } } } + + + @Override + public byte[] getParams() { + return aasParams.getBytes(); + } @Override public ArrayList createEnclaveSession(SQLServerConnection connection, String userSql, diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 94a80d534..8cc094c60 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -6479,7 +6479,7 @@ boolean isAEv2() { return (aeVersion >= TDS.COLUMNENCRYPTION_VERSION2); } - ISQLServerEnclaveProvider enclaveProvider; + public ISQLServerEnclaveProvider enclaveProvider; ArrayList initEnclaveParameters(String userSql, String preparedTypeDefinitions, Parameter[] params, ArrayList parameterNames) throws SQLServerException { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java index 51f02a21a..99ccab9b3 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java @@ -52,6 +52,11 @@ public void getAttestationParameters(String url) throws SQLServerException { vsmParams = new VSMAttestationParameters(); } } + + @Override + public byte[] getParams() { + return vsmParams.getBytes(); + } @Override public ArrayList createEnclaveSession(SQLServerConnection connection, String userSql, diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java index 2a75721ab..d5e5de783 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java @@ -1248,6 +1248,16 @@ private void testAlterColumnEncryption(SQLServerStatement stmt, String tableName if (!TestUtils.isAEv2(con)) { fail(TestResource.getResource("R_expectedExceptionNotThrown")); } else { + byte[] bytes = con.enclaveProvider.getParams(); + char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + System.out.println("Attestation Params: " + new String(hexChars)); + e.printStackTrace(); fail(TestResource.getResource("R_AlterAEv2Error") + e.getMessage() + "Query: " + sql); } } From 8d14b4b13c506a42adc3d26376bc53c6b62b17f8 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Fri, 6 Dec 2019 12:49:06 -0800 Subject: [PATCH 23/25] print stack trace --- .../sqlserver/jdbc/ISQLServerEnclaveProvider.java | 2 -- .../jdbc/SQLServerAASEnclaveProvider.java | 6 ------ .../jdbc/SQLServerVSMEnclaveProvider.java | 14 -------------- .../JDBCEncryptionDecryptionTest.java | 9 --------- 4 files changed, 31 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java index ecaeee571..6cdc4cce3 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java @@ -71,8 +71,6 @@ default byte[] getEnclavePackage(String userSQL, ArrayList enclaveCEKs) } return null; } - - public byte[] getParams(); /** * Returns the attestation parameters diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java index 83c24b728..7f167d8b1 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java @@ -63,12 +63,6 @@ public void getAttestationParameters(String url) throws SQLServerException { } } } - - - @Override - public byte[] getParams() { - return aasParams.getBytes(); - } @Override public ArrayList createEnclaveSession(SQLServerConnection connection, String userSql, diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java index 99ccab9b3..8d758cd3d 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java @@ -52,11 +52,6 @@ public void getAttestationParameters(String url) throws SQLServerException { vsmParams = new VSMAttestationParameters(); } } - - @Override - public byte[] getParams() { - return vsmParams.getBytes(); - } @Override public ArrayList createEnclaveSession(SQLServerConnection connection, String userSql, @@ -287,15 +282,6 @@ private ArrayList describeParameterEncryption(SQLServerConnection connec rs.close(); } catch (SQLException e) { if (e instanceof SQLServerException) { - char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); - byte[] bytes = vsmParams.getBytes(); - char[] hexChars = new char[bytes.length * 2]; - for (int j = 0; j < bytes.length; j++) { - int v = bytes[j] & 0xFF; - hexChars[j * 2] = HEX_ARRAY[v >>> 4]; - hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; - } - System.out.println("Attestation Params: " + new String(hexChars)); throw (SQLServerException) e; } else { throw new SQLServerException(SQLServerException.getErrString("R_UnableRetrieveParameterMetadata"), null, diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java index d5e5de783..ee92ce495 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java @@ -1248,15 +1248,6 @@ private void testAlterColumnEncryption(SQLServerStatement stmt, String tableName if (!TestUtils.isAEv2(con)) { fail(TestResource.getResource("R_expectedExceptionNotThrown")); } else { - byte[] bytes = con.enclaveProvider.getParams(); - char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); - char[] hexChars = new char[bytes.length * 2]; - for (int j = 0; j < bytes.length; j++) { - int v = bytes[j] & 0xFF; - hexChars[j * 2] = HEX_ARRAY[v >>> 4]; - hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; - } - System.out.println("Attestation Params: " + new String(hexChars)); e.printStackTrace(); fail(TestResource.getResource("R_AlterAEv2Error") + e.getMessage() + "Query: " + sql); } From c9b91bab1b0c27c50f3f426458c312f74ff47568 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Fri, 6 Dec 2019 14:28:49 -0800 Subject: [PATCH 24/25] shrink code --- .../jdbc/ISQLServerEnclaveProvider.java | 117 ++++++++++++++++ .../jdbc/SQLServerAASEnclaveProvider.java | 132 +----------------- .../jdbc/SQLServerVSMEnclaveProvider.java | 130 +---------------- 3 files changed, 125 insertions(+), 254 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java index 6cdc4cce3..71b1bc2b4 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java @@ -26,10 +26,16 @@ import java.security.spec.ECPoint; import java.security.spec.ECPublicKeySpec; import java.security.spec.RSAPublicKeySpec; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.text.MessageFormat; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.Hashtable; +import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.atomic.AtomicInteger; @@ -42,6 +48,8 @@ * */ public interface ISQLServerEnclaveProvider { + static final String proc = "EXEC sp_describe_parameter_encryption ?,?,?"; + default byte[] getEnclavePackage(String userSQL, ArrayList enclaveCEKs) throws SQLServerException { EnclaveSession enclaveSession = getEnclaveSession(); if (null != enclaveSession) { @@ -72,6 +80,115 @@ default byte[] getEnclavePackage(String userSQL, ArrayList enclaveCEKs) return null; } + default ResultSet executeProc(PreparedStatement stmt, String userSql, String preparedTypeDefinitions, + BaseAttestationRequest req) throws SQLException { + ((SQLServerPreparedStatement) stmt).isInternalEncryptionQuery = true; + stmt.setNString(1, userSql); + if (preparedTypeDefinitions != null && preparedTypeDefinitions.length() != 0) { + stmt.setNString(2, preparedTypeDefinitions); + } else { + stmt.setNString(2, ""); + } + stmt.setBytes(3, req.getBytes()); + return ((SQLServerPreparedStatement) stmt).executeQueryInternal(); + } + + default void processAev1SPDE(String userSql, String preparedTypeDefinitions, Parameter[] params, + ArrayList parameterNames, SQLServerConnection connection, PreparedStatement stmt, ResultSet rs, + ArrayList enclaveRequestedCEKs, BaseAttestationRequest attestationReq) throws SQLException { + if (null == rs) { + return; + } + + Map cekList = new HashMap<>(); + CekTableEntry cekEntry = null; + boolean isRequestedByEnclave = false; + while (rs.next()) { + int currentOrdinal = rs.getInt(DescribeParameterEncryptionResultSet1.KeyOrdinal.value()); + if (!cekList.containsKey(currentOrdinal)) { + cekEntry = new CekTableEntry(currentOrdinal); + cekList.put(cekEntry.ordinal, cekEntry); + } else { + cekEntry = cekList.get(currentOrdinal); + } + + String keyStoreName = rs.getString(DescribeParameterEncryptionResultSet1.ProviderName.value()); + String algo = rs.getString(DescribeParameterEncryptionResultSet1.KeyEncryptionAlgorithm.value()); + String keyPath = rs.getString(DescribeParameterEncryptionResultSet1.KeyPath.value()); + + int dbID = rs.getInt(DescribeParameterEncryptionResultSet1.DbId.value()); + byte[] mdVer = rs.getBytes(DescribeParameterEncryptionResultSet1.KeyMdVersion.value()); + int keyID = rs.getInt(DescribeParameterEncryptionResultSet1.KeyId.value()); + byte[] encryptedKey = rs.getBytes(DescribeParameterEncryptionResultSet1.EncryptedKey.value()); + + cekEntry.add(encryptedKey, dbID, keyID, rs.getInt(DescribeParameterEncryptionResultSet1.KeyVersion.value()), + mdVer, keyPath, keyStoreName, algo); + + // servers supporting enclave computations should always return a boolean indicating whether the key + // is + // required by enclave or not. + if (ColumnEncryptionVersion.AE_v2.value() <= connection.getServerColumnEncryptionVersion().value()) { + isRequestedByEnclave = rs + .getBoolean(DescribeParameterEncryptionResultSet1.IsRequestedByEnclave.value()); + } + + if (isRequestedByEnclave) { + byte[] keySignature = rs.getBytes(DescribeParameterEncryptionResultSet1.EnclaveCMKSignature.value()); + String serverName = connection.getTrustedServerNameAE(); + SQLServerSecurityUtility.verifyColumnMasterKeyMetadata(connection, keyStoreName, keyPath, serverName, + isRequestedByEnclave, keySignature); + + // DBID(4) + MDVER(8) + KEYID(2) + CEK(32) = 46 + ByteBuffer aev2CekEntry = ByteBuffer.allocate(46); + aev2CekEntry.order(ByteOrder.LITTLE_ENDIAN).putInt(dbID); + aev2CekEntry.put(mdVer); + aev2CekEntry.putShort((short) keyID); + aev2CekEntry.put(connection.getColumnEncryptionKeyStoreProvider(keyStoreName) + .decryptColumnEncryptionKey(keyPath, algo, encryptedKey)); + enclaveRequestedCEKs.add(aev2CekEntry.array()); + } + } + + // Process the second resultset. + if (!stmt.getMoreResults()) { + throw new SQLServerException(this, SQLServerException.getErrString("R_UnexpectedDescribeParamFormat"), null, + 0, false); + } + + rs = (SQLServerResultSet) stmt.getResultSet(); + while (rs.next() && null != params) { + String paramName = rs.getString(DescribeParameterEncryptionResultSet2.ParameterName.value()); + int paramIndex = parameterNames.indexOf(paramName); + int cekOrdinal = rs.getInt(DescribeParameterEncryptionResultSet2.ColumnEncryptionKeyOrdinal.value()); + cekEntry = cekList.get(cekOrdinal); + + // cekEntry will be null if none of the parameters are encrypted. + if ((null != cekEntry) && (cekList.size() < cekOrdinal)) { + MessageFormat form = new MessageFormat( + SQLServerException.getErrString("R_InvalidEncryptionKeyOrdinal")); + Object[] msgArgs = {cekOrdinal, cekEntry.getSize()}; + throw new SQLServerException(this, form.format(msgArgs), null, 0, false); + } + SQLServerEncryptionType encType = SQLServerEncryptionType + .of((byte) rs.getInt(DescribeParameterEncryptionResultSet2.ColumnEncrytionType.value())); + if (SQLServerEncryptionType.PlainText != encType) { + params[paramIndex].cryptoMeta = new CryptoMetadata(cekEntry, (short) cekOrdinal, + (byte) rs.getInt(DescribeParameterEncryptionResultSet2.ColumnEncryptionAlgorithm.value()), null, + encType.value, + (byte) rs.getInt(DescribeParameterEncryptionResultSet2.NormalizationRuleVersion.value())); + // Decrypt the symmetric key.(This will also validate and throw if needed). + SQLServerSecurityUtility.decryptSymmetricKey(params[paramIndex].cryptoMeta, connection); + } else { + if (params[paramIndex].getForceEncryption()) { + MessageFormat form = new MessageFormat( + SQLServerException.getErrString("R_ForceEncryptionTrue_HonorAETrue_UnencryptedColumn")); + Object[] msgArgs = {userSql, paramIndex + 1}; + SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), "0", true); + } + } + } + } + /** * Returns the attestation parameters * diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java index 7f167d8b1..9791eb061 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java @@ -22,14 +22,11 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.text.MessageFormat; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; -import java.util.HashMap; import java.util.Hashtable; -import java.util.Map; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -120,135 +117,15 @@ private ArrayList describeParameterEncryption(SQLServerConnection connec ArrayList parameterNames) throws SQLServerException { ArrayList enclaveRequestedCEKs = new ArrayList<>(); ResultSet rs = null; - try (PreparedStatement stmt = connection.prepareStatement("EXEC sp_describe_parameter_encryption ?,?,?")) { - ((SQLServerPreparedStatement) stmt).isInternalEncryptionQuery = true; - stmt.setNString(1, userSql); - if (preparedTypeDefinitions != null && preparedTypeDefinitions.length() != 0) { - stmt.setNString(2, preparedTypeDefinitions); - } else { - stmt.setNString(2, ""); - } - stmt.setBytes(3, aasParams.getBytes()); - rs = ((SQLServerPreparedStatement) stmt).executeQueryInternal(); - + try (PreparedStatement stmt = connection.prepareStatement(proc)) { + rs = executeProc(stmt, userSql, preparedTypeDefinitions, aasParams); if (null == rs) { // No results. Meaning no parameter. // Should never happen. return enclaveRequestedCEKs; } - - Map cekList = new HashMap<>(); - CekTableEntry cekEntry = null; - boolean isRequestedByEnclave = false; - try { - while (rs.next()) { - int currentOrdinal = rs.getInt(DescribeParameterEncryptionResultSet1.KeyOrdinal.value()); - if (!cekList.containsKey(currentOrdinal)) { - cekEntry = new CekTableEntry(currentOrdinal); - cekList.put(cekEntry.ordinal, cekEntry); - } else { - cekEntry = cekList.get(currentOrdinal); - } - - String keyStoreName = rs.getString(DescribeParameterEncryptionResultSet1.ProviderName.value()); - String algo = rs.getString(DescribeParameterEncryptionResultSet1.KeyEncryptionAlgorithm.value()); - String keyPath = rs.getString(DescribeParameterEncryptionResultSet1.KeyPath.value()); - - int dbID = rs.getInt(DescribeParameterEncryptionResultSet1.DbId.value()); - byte[] mdVer = rs.getBytes(DescribeParameterEncryptionResultSet1.KeyMdVersion.value()); - int keyID = rs.getInt(DescribeParameterEncryptionResultSet1.KeyId.value()); - byte[] encryptedKey = rs.getBytes(DescribeParameterEncryptionResultSet1.EncryptedKey.value()); - - cekEntry.add(encryptedKey, dbID, keyID, - rs.getInt(DescribeParameterEncryptionResultSet1.KeyVersion.value()), mdVer, keyPath, - keyStoreName, algo); - - /* - * servers supporting enclave computations should always return a boolean indicating whether the key - * is required by enclave or not. - */ - if (ColumnEncryptionVersion.AE_v2.value() <= connection.getServerColumnEncryptionVersion() - .value()) { - isRequestedByEnclave = rs - .getBoolean(DescribeParameterEncryptionResultSet1.IsRequestedByEnclave.value()); - } - - if (isRequestedByEnclave) { - byte[] keySignature = rs - .getBytes(DescribeParameterEncryptionResultSet1.EnclaveCMKSignature.value()); - String serverName = connection.getTrustedServerNameAE(); - SQLServerSecurityUtility.verifyColumnMasterKeyMetadata(connection, keyStoreName, keyPath, - serverName, isRequestedByEnclave, keySignature); - - // DBID(4) + MDVER(8) + KEYID(2) + CEK(32) = 46 - ByteBuffer aev2CekEntry = ByteBuffer.allocate(46); - aev2CekEntry.order(ByteOrder.LITTLE_ENDIAN).putInt(dbID); - aev2CekEntry.put(mdVer); - aev2CekEntry.putShort((short) keyID); - aev2CekEntry.put(connection.getColumnEncryptionKeyStoreProvider(keyStoreName) - .decryptColumnEncryptionKey(keyPath, algo, encryptedKey)); - enclaveRequestedCEKs.add(aev2CekEntry.array()); - } - } - } catch (SQLException e) { - if (e instanceof SQLServerException) { - throw (SQLServerException) e; - } else { - throw new SQLServerException(SQLServerException.getErrString("R_UnableRetrieveParameterMetadata"), - null, 0, e); - } - } - - // Process the second resultset. - if (!stmt.getMoreResults()) { - throw new SQLServerException(this, SQLServerException.getErrString("R_UnexpectedDescribeParamFormat"), - null, 0, false); - } - - try { - rs = (SQLServerResultSet) stmt.getResultSet(); - while (rs.next() && null != params) { - String paramName = rs.getString(DescribeParameterEncryptionResultSet2.ParameterName.value()); - int paramIndex = parameterNames.indexOf(paramName); - int cekOrdinal = rs - .getInt(DescribeParameterEncryptionResultSet2.ColumnEncryptionKeyOrdinal.value()); - cekEntry = cekList.get(cekOrdinal); - - // cekEntry will be null if none of the parameters are encrypted. - if ((null != cekEntry) && (cekList.size() < cekOrdinal)) { - MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_InvalidEncryptionKeyOrdinal")); - Object[] msgArgs = {cekOrdinal, cekEntry.getSize()}; - throw new SQLServerException(this, form.format(msgArgs), null, 0, false); - } - SQLServerEncryptionType encType = SQLServerEncryptionType - .of((byte) rs.getInt(DescribeParameterEncryptionResultSet2.ColumnEncrytionType.value())); - if (SQLServerEncryptionType.PlainText != encType) { - params[paramIndex].cryptoMeta = new CryptoMetadata(cekEntry, (short) cekOrdinal, - (byte) rs.getInt( - DescribeParameterEncryptionResultSet2.ColumnEncryptionAlgorithm.value()), - null, encType.value, (byte) rs.getInt( - DescribeParameterEncryptionResultSet2.NormalizationRuleVersion.value())); - // Decrypt the symmetric key.(This will also validate and throw if needed). - SQLServerSecurityUtility.decryptSymmetricKey(params[paramIndex].cryptoMeta, connection); - } else { - if (params[paramIndex].getForceEncryption()) { - MessageFormat form = new MessageFormat(SQLServerException - .getErrString("R_ForceEncryptionTrue_HonorAETrue_UnencryptedColumn")); - Object[] msgArgs = {userSql, paramIndex + 1}; - SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), "0", true); - } - } - } - } catch (SQLException e) { - if (e instanceof SQLServerException) { - throw (SQLServerException) e; - } else { - throw new SQLServerException(SQLServerException.getErrString("R_UnableRetrieveParameterMetadata"), - null, 0, e); - } - } - + processAev1SPDE(userSql, preparedTypeDefinitions, params, parameterNames, connection, stmt, rs, + enclaveRequestedCEKs, aasParams); // Process the third resultset. if (connection.isAEv2() && stmt.getMoreResults()) { rs = (SQLServerResultSet) stmt.getResultSet(); @@ -260,7 +137,6 @@ private ArrayList describeParameterEncryption(SQLServerConnection connec } } } - // Null check for rs is done already. rs.close(); } catch (SQLException e) { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java index 8d758cd3d..dd2e424f0 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java @@ -25,9 +25,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.Hashtable; -import java.util.Map; /** @@ -138,134 +136,15 @@ private ArrayList describeParameterEncryption(SQLServerConnection connec ArrayList parameterNames) throws SQLServerException { ArrayList enclaveRequestedCEKs = new ArrayList<>(); ResultSet rs = null; - try (PreparedStatement stmt = connection.prepareStatement("EXEC sp_describe_parameter_encryption ?,?,?")) { - ((SQLServerPreparedStatement) stmt).isInternalEncryptionQuery = true; - stmt.setNString(1, userSql); - if (preparedTypeDefinitions != null && preparedTypeDefinitions.length() != 0) { - stmt.setNString(2, preparedTypeDefinitions); - } else { - stmt.setNString(2, ""); - } - stmt.setBytes(3, vsmParams.getBytes()); - rs = ((SQLServerPreparedStatement) stmt).executeQueryInternal(); - + try (PreparedStatement stmt = connection.prepareStatement(proc)) { + rs = executeProc(stmt, userSql, preparedTypeDefinitions, vsmParams); if (null == rs) { // No results. Meaning no parameter. // Should never happen. return enclaveRequestedCEKs; } - - Map cekList = new HashMap<>(); - CekTableEntry cekEntry = null; - boolean isRequestedByEnclave = false; - try { - while (rs.next()) { - int currentOrdinal = rs.getInt(DescribeParameterEncryptionResultSet1.KeyOrdinal.value()); - if (!cekList.containsKey(currentOrdinal)) { - cekEntry = new CekTableEntry(currentOrdinal); - cekList.put(cekEntry.ordinal, cekEntry); - } else { - cekEntry = cekList.get(currentOrdinal); - } - - String keyStoreName = rs.getString(DescribeParameterEncryptionResultSet1.ProviderName.value()); - String algo = rs.getString(DescribeParameterEncryptionResultSet1.KeyEncryptionAlgorithm.value()); - String keyPath = rs.getString(DescribeParameterEncryptionResultSet1.KeyPath.value()); - - int dbID = rs.getInt(DescribeParameterEncryptionResultSet1.DbId.value()); - byte[] mdVer = rs.getBytes(DescribeParameterEncryptionResultSet1.KeyMdVersion.value()); - int keyID = rs.getInt(DescribeParameterEncryptionResultSet1.KeyId.value()); - byte[] encryptedKey = rs.getBytes(DescribeParameterEncryptionResultSet1.EncryptedKey.value()); - - cekEntry.add(encryptedKey, dbID, keyID, - rs.getInt(DescribeParameterEncryptionResultSet1.KeyVersion.value()), mdVer, keyPath, - keyStoreName, algo); - - // servers supporting enclave computations should always return a boolean indicating whether the key - // is - // required by enclave or not. - if (ColumnEncryptionVersion.AE_v2.value() <= connection.getServerColumnEncryptionVersion() - .value()) { - isRequestedByEnclave = rs - .getBoolean(DescribeParameterEncryptionResultSet1.IsRequestedByEnclave.value()); - } - - if (isRequestedByEnclave) { - byte[] keySignature = rs - .getBytes(DescribeParameterEncryptionResultSet1.EnclaveCMKSignature.value()); - String serverName = connection.getTrustedServerNameAE(); - SQLServerSecurityUtility.verifyColumnMasterKeyMetadata(connection, keyStoreName, keyPath, - serverName, isRequestedByEnclave, keySignature); - - // DBID(4) + MDVER(8) + KEYID(2) + CEK(32) = 46 - ByteBuffer aev2CekEntry = ByteBuffer.allocate(46); - aev2CekEntry.order(ByteOrder.LITTLE_ENDIAN).putInt(dbID); - aev2CekEntry.put(mdVer); - aev2CekEntry.putShort((short) keyID); - aev2CekEntry.put(connection.getColumnEncryptionKeyStoreProvider(keyStoreName) - .decryptColumnEncryptionKey(keyPath, algo, encryptedKey)); - enclaveRequestedCEKs.add(aev2CekEntry.array()); - } - } - } catch (SQLException e) { - if (e instanceof SQLServerException) { - throw (SQLServerException) e; - } else { - throw new SQLServerException(SQLServerException.getErrString("R_UnableRetrieveParameterMetadata"), - null, 0, e); - } - } - - // Process the second resultset. - if (!stmt.getMoreResults()) { - throw new SQLServerException(this, SQLServerException.getErrString("R_UnexpectedDescribeParamFormat"), - null, 0, false); - } - - try { - rs = (SQLServerResultSet) stmt.getResultSet(); - while (rs.next() && null != params) { - String paramName = rs.getString(DescribeParameterEncryptionResultSet2.ParameterName.value()); - int paramIndex = parameterNames.indexOf(paramName); - int cekOrdinal = rs - .getInt(DescribeParameterEncryptionResultSet2.ColumnEncryptionKeyOrdinal.value()); - cekEntry = cekList.get(cekOrdinal); - - // cekEntry will be null if none of the parameters are encrypted. - if ((null != cekEntry) && (cekList.size() < cekOrdinal)) { - MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_InvalidEncryptionKeyOrdinal")); - Object[] msgArgs = {cekOrdinal, cekEntry.getSize()}; - throw new SQLServerException(this, form.format(msgArgs), null, 0, false); - } - SQLServerEncryptionType encType = SQLServerEncryptionType - .of((byte) rs.getInt(DescribeParameterEncryptionResultSet2.ColumnEncrytionType.value())); - if (SQLServerEncryptionType.PlainText != encType) { - params[paramIndex].cryptoMeta = new CryptoMetadata(cekEntry, (short) cekOrdinal, - (byte) rs.getInt( - DescribeParameterEncryptionResultSet2.ColumnEncryptionAlgorithm.value()), - null, encType.value, (byte) rs.getInt( - DescribeParameterEncryptionResultSet2.NormalizationRuleVersion.value())); - // Decrypt the symmetric key.(This will also validate and throw if needed). - SQLServerSecurityUtility.decryptSymmetricKey(params[paramIndex].cryptoMeta, connection); - } else { - if (params[paramIndex].getForceEncryption()) { - MessageFormat form = new MessageFormat(SQLServerException - .getErrString("R_ForceEncryptionTrue_HonorAETrue_UnencryptedColumn")); - Object[] msgArgs = {userSql, paramIndex + 1}; - SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), "0", true); - } - } - } - } catch (SQLException e) { - if (e instanceof SQLServerException) { - throw (SQLServerException) e; - } else { - throw new SQLServerException(SQLServerException.getErrString("R_UnableRetrieveParameterMetadata"), - null, 0, e); - } - } - + processAev1SPDE(userSql, preparedTypeDefinitions, params, parameterNames, connection, stmt, rs, + enclaveRequestedCEKs, vsmParams); // Process the third resultset. if (connection.isAEv2() && stmt.getMoreResults()) { rs = (SQLServerResultSet) stmt.getResultSet(); @@ -277,7 +156,6 @@ private ArrayList describeParameterEncryption(SQLServerConnection connec } } } - // Null check for rs is done already. rs.close(); } catch (SQLException e) { From 13d041a10a76748442cf1739517978af747fdbae Mon Sep 17 00:00:00 2001 From: rene-ye Date: Fri, 6 Dec 2019 14:57:06 -0800 Subject: [PATCH 25/25] cleanup --- .../microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java | 6 +----- .../sqlserver/jdbc/SQLServerAASEnclaveProvider.java | 2 +- .../com/microsoft/sqlserver/jdbc/SQLServerConnection.java | 2 +- .../sqlserver/jdbc/SQLServerVSMEnclaveProvider.java | 2 +- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java index 71b1bc2b4..4576e7840 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java @@ -95,11 +95,7 @@ default ResultSet executeProc(PreparedStatement stmt, String userSql, String pre default void processAev1SPDE(String userSql, String preparedTypeDefinitions, Parameter[] params, ArrayList parameterNames, SQLServerConnection connection, PreparedStatement stmt, ResultSet rs, - ArrayList enclaveRequestedCEKs, BaseAttestationRequest attestationReq) throws SQLException { - if (null == rs) { - return; - } - + ArrayList enclaveRequestedCEKs) throws SQLException { Map cekList = new HashMap<>(); CekTableEntry cekEntry = null; boolean isRequestedByEnclave = false; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java index 9791eb061..0fffc9bae 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAASEnclaveProvider.java @@ -125,7 +125,7 @@ private ArrayList describeParameterEncryption(SQLServerConnection connec return enclaveRequestedCEKs; } processAev1SPDE(userSql, preparedTypeDefinitions, params, parameterNames, connection, stmt, rs, - enclaveRequestedCEKs, aasParams); + enclaveRequestedCEKs); // Process the third resultset. if (connection.isAEv2() && stmt.getMoreResults()) { rs = (SQLServerResultSet) stmt.getResultSet(); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 8cc094c60..3701de78b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -6479,7 +6479,7 @@ boolean isAEv2() { return (aeVersion >= TDS.COLUMNENCRYPTION_VERSION2); } - public ISQLServerEnclaveProvider enclaveProvider; + private ISQLServerEnclaveProvider enclaveProvider; ArrayList initEnclaveParameters(String userSql, String preparedTypeDefinitions, Parameter[] params, ArrayList parameterNames) throws SQLServerException { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java index dd2e424f0..416a6a8a7 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerVSMEnclaveProvider.java @@ -144,7 +144,7 @@ private ArrayList describeParameterEncryption(SQLServerConnection connec return enclaveRequestedCEKs; } processAev1SPDE(userSql, preparedTypeDefinitions, params, parameterNames, connection, stmt, rs, - enclaveRequestedCEKs, vsmParams); + enclaveRequestedCEKs); // Process the third resultset. if (connection.isAEv2() && stmt.getMoreResults()) { rs = (SQLServerResultSet) stmt.getResultSet();