net.jsign.PVK Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jsign-core Show documentation
Show all versions of jsign-core Show documentation
Pure Java implementation of Microsoft Authenticode for signing Windows executable files
/**
* 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 net.jsign;
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 PVK file information
* @see OpenSSL PVK format implementation
*
* @author Emmanuel Bourg
* @since 1.0
*/
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 MSDN - Private Key BLOBs
*/
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;
}
}