Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

change logic for decryptprovider #12

Merged
merged 3 commits into from
Mar 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 213 additions & 0 deletions src/main/java/com/microsoft/sqlserver/jdbc/PVK.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/**
* Copyright 2012 Emmanuel Bourg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.sqlserver.jdbc;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

/**
* PVK file parser. Based on the documentation and the code from Stephen Norman Henson.
*
* @see <a href="http://www.drh-consultancy.demon.co.uk/pvk.html">PVK file information</a>
* @see <a href="https://github.com/openssl/openssl/blob/OpenSSL_1_1_0-stable/crypto/pem/pvkfmt.c">OpenSSL PVK format implementation</a>
*
* @author Emmanuel Bourg
* @since 1.0
*/
public class PVK {

/** Header signature of PVK files */
private static final long PVK_MAGIC = 0xB0B5F11EL;

/** Header of the unencrypted key */
private static final byte[] RSA2_KEY_MAGIC = "RSA2".getBytes();

private PVK() {
}

public static PrivateKey parse(File file, String password) throws GeneralSecurityException, IOException {
ByteBuffer buffer = ByteBuffer.allocate((int) file.length());

try (FileInputStream in = new FileInputStream(file)) {
in.getChannel().read(buffer);
return parse(buffer, password);
}
}

public static PrivateKey parse(ByteBuffer buffer, String password) throws GeneralSecurityException {
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.rewind();

long magic = buffer.getInt() & 0xFFFFFFFFL;
if (PVK_MAGIC != magic) {
throw new IllegalArgumentException("PVK header signature not found");
}

buffer.position(buffer.position() + 4); // reserved
int keyType = buffer.getInt();
boolean encrypted = buffer.getInt() != 0;
int saltLength = buffer.getInt();
int keyLength = buffer.getInt();
byte[] salt = new byte[saltLength];
buffer.get(salt);

// BLOBHEADER structure: https://msdn.microsoft.com/en-us/library/cc235198.aspx
byte btype = buffer.get();
byte version = buffer.get();
buffer.position(buffer.position() + 2); // reserved
int keyalg = buffer.getInt();

byte[] key = new byte[keyLength - 8];
buffer.get(key);

if (encrypted) {
key = decrypt(key, salt, password);
}

return parseKey(key);
}

private static byte[] decrypt(byte[] encoded, byte[] salt, String password) throws GeneralSecurityException {
byte[] hash = deriveKey(salt, password);
String algorithm = "RC4";

SecretKey strongKey = new SecretKeySpec(hash, 0, 16, algorithm);
byte[] decoded = decrypt(strongKey, encoded);
if (startsWith(decoded, RSA2_KEY_MAGIC)) {
return decoded;
}

// trim the key to 40 bits
Arrays.fill(hash, 5, hash.length, (byte) 0);
SecretKey weakKey = new SecretKeySpec(hash, 0, 16, algorithm);
decoded = decrypt(weakKey, encoded);
if (startsWith(decoded, RSA2_KEY_MAGIC)) {
return decoded;
}

throw new IllegalArgumentException("Unable to decrypt the PVK key, please verify the password");
}

private static byte[] decrypt(SecretKey key, byte[] encoded) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance(key.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, key);
return cipher.doFinal(encoded);
}

/**
* Key derivation SHA1(salt + password)
*/
private static byte[] deriveKey(byte[] salt, String password) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA1");
digest.update(salt);
if (password != null) {
digest.update(password.getBytes());
}
return digest.digest();
}

/**
* Parses a private key blob.
*
* @see <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa375601.aspx#priv_BLOB">MSDN - Private Key BLOBs</a>
*/
private static PrivateKey parseKey(byte[] key) throws GeneralSecurityException {
ByteBuffer buffer = ByteBuffer.wrap(key);
buffer.order(ByteOrder.LITTLE_ENDIAN);

if (!startsWith(key, RSA2_KEY_MAGIC)) {
throw new IllegalArgumentException("Unable to parse the PVK key, unsupported key format: " + new String(key, 0, RSA2_KEY_MAGIC.length));
}

buffer.position(buffer.position() + RSA2_KEY_MAGIC.length); // skip the header

int bitlength = buffer.getInt();
BigInteger publicExponent = new BigInteger(String.valueOf(buffer.getInt()));

int l = bitlength / 8;

BigInteger modulus = getBigInteger(buffer, l);
BigInteger primeP = getBigInteger(buffer, l / 2);
BigInteger primeQ = getBigInteger(buffer, l / 2);
BigInteger primeExponentP = getBigInteger(buffer, l / 2);
BigInteger primeExponentQ = getBigInteger(buffer, l / 2);
BigInteger crtCoefficient = getBigInteger(buffer, l / 2);
BigInteger privateExponent = getBigInteger(buffer, l);

RSAPrivateCrtKeySpec spec = new RSAPrivateCrtKeySpec(modulus, publicExponent, privateExponent, primeP, primeQ, primeExponentP, primeExponentQ, crtCoefficient);
KeyFactory factory = KeyFactory.getInstance("RSA");

return factory.generatePrivate(spec);
}

/**
* Read a BigInteger from the specified buffer (in little-endian byte-order).
*
* @param buffer the input buffer
* @param length the number of bytes read
*/
private static BigInteger getBigInteger(ByteBuffer buffer, int length) {
byte[] array = new byte[length + 1];
buffer.get(array, 0, length);

return new BigInteger(reverse(array));
}

/**
* Reverses the specified array.
*/
private static byte[] reverse(byte[] array) {
for (int i = 0; i < array.length / 2; i++) {
byte b = array[i];
array[i] = array[array.length - 1 - i];
array[array.length - 1 - i] = b;
}

return array;
}

/**
* Tells if an array starts with the specified prefix.
*/
private static boolean startsWith(byte[] array, byte[] prefix) {
for (int i = 0; i < prefix.length; i++) {
if (prefix[i] != array[i]) {
return false;
}
}

return true;
}
}





Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package com.microsoft.sqlserver.jdbc;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
Expand Down Expand Up @@ -75,36 +79,36 @@ private static KeyManager[] readPKCS8Certificate(String certPath, String keyPath

private static Certificate loadCertificate(String certificatePem) throws IOException, GeneralSecurityException {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
final byte[] content = readPemContent(certificatePem);
return certificateFactory.generateCertificate(new ByteArrayInputStream(content));
InputStream certstream = fullStream(certificatePem);
return certificateFactory.generateCertificate(certstream);
}

private static InputStream fullStream(String fname) throws IOException {
FileInputStream fis = new FileInputStream(fname);
DataInputStream dis = new DataInputStream(fis);
byte[] bytes = new byte[dis.available()];
dis.readFully(bytes);
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
return bais;
}

private static PrivateKey loadPrivateKey(String privateKeyPem,
String privateKeyPass) throws IOException, GeneralSecurityException {
return pemLoadPrivateKeyPkcs1OrPkcs8Encoded(privateKeyPem, privateKeyPass);
}

private static byte[] readPemContent(String pemPath) throws IOException {
final byte[] content;
FileInputStream fis = new FileInputStream(pemPath);
try (PemReader pemReader = new PemReader(new InputStreamReader(fis))) {
final PemObject pemObject = pemReader.readPemObject();
content = pemObject.getContent();
}
return content;
}

private static String getStringFromFile(String filePath) throws FileNotFoundException {
Scanner scanner = null;
try {
scanner = new Scanner(new File(filePath));
String text = scanner.useDelimiter("\\A").next();
return text;
} finally {
if (scanner != null) {
scanner.close();
}
}
// private static byte[] readPemContent(String pemPath) throws IOException {
// final byte[] content;
// FileInputStream fis = new FileInputStream(pemPath);
// try (PemReader pemReader = new PemReader(new InputStreamReader(fis))) {
// final PemObject pemObject = pemReader.readPemObject();
// content = pemObject.getContent();
// }
// return content;
// }

private static String getStringFromFile(String filePath) throws IOException {
return new String(Files.readAllBytes(Paths.get(filePath)));
}

private static PrivateKey pemLoadPrivateKeyPkcs1OrPkcs8Encoded(String privateKeyPemPath,
Expand Down Expand Up @@ -137,11 +141,11 @@ private static PrivateKey pemLoadPrivateKeyPkcs1OrPkcs8Encoded(String privateKey
try {
pemParser = new PEMParser(new StringReader(privateKeyPem));
Object object = pemParser.readObject();
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder()
.build(privateKeyPassword.toCharArray());
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
KeyPair kp;
if (object instanceof PEMEncryptedKeyPair) {
if (object instanceof PEMEncryptedKeyPair && privateKeyPassword != null) {
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder()
.build(privateKeyPassword.toCharArray());
kp = converter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(decProv));
} else {
kp = converter.getKeyPair((PEMKeyPair) object);
Expand All @@ -150,7 +154,8 @@ private static PrivateKey pemLoadPrivateKeyPkcs1OrPkcs8Encoded(String privateKey
} finally {
pemParser.close();
}
} else {
return PVK.parse(new File(privateKeyPemPath), privateKeyPassword);
}
throw new GeneralSecurityException("Not supported format of a private key");
}
}