reformat all zipsigner code with Android Studio Ctrl-Alt-L

This commit is contained in:
Hans-Christoph Steiner 2018-04-19 16:18:24 +02:00
parent a3d9850a42
commit 4e4dd2385b
37 changed files with 1339 additions and 1385 deletions

View File

@ -13,84 +13,81 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.logging;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;
public abstract class AbstractLogger implements LoggerInterface
{
public abstract class AbstractLogger implements LoggerInterface {
protected String category;
protected String category;
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
public AbstractLogger( String category) {
this.category = category;
}
public AbstractLogger(String category) {
this.category = category;
}
protected String format( String level, String message) {
return String.format( "%s %s %s: %s\n", dateFormat.format(new Date()), level, category, message);
}
protected String format(String level, String message) {
return String.format("%s %s %s: %s\n", dateFormat.format(new Date()), level, category, message);
}
protected abstract void write( String level, String message, Throwable t);
protected abstract void write(String level, String message, Throwable t);
protected void writeFixNullMessage( String level, String message, Throwable t) {
protected void writeFixNullMessage(String level, String message, Throwable t) {
if (message == null) {
if (t != null) message = t.getClass().getName();
else message = "null";
}
write( level, message, t);
write(level, message, t);
}
public void debug(String message, Throwable t) {
writeFixNullMessage( DEBUG, message, t);
}
public void debug(String message, Throwable t) {
writeFixNullMessage(DEBUG, message, t);
}
public void debug(String message) {
writeFixNullMessage( DEBUG, message, null);
}
public void debug(String message) {
writeFixNullMessage(DEBUG, message, null);
}
public void error(String message, Throwable t) {
writeFixNullMessage( ERROR, message, t);
}
public void error(String message, Throwable t) {
writeFixNullMessage(ERROR, message, t);
}
public void error(String message) {
writeFixNullMessage( ERROR, message, null);
}
public void error(String message) {
writeFixNullMessage(ERROR, message, null);
}
public void info(String message, Throwable t) {
writeFixNullMessage( INFO, message, t);
}
public void info(String message, Throwable t) {
writeFixNullMessage(INFO, message, t);
}
public void info(String message) {
writeFixNullMessage( INFO, message, null);
}
public void info(String message) {
writeFixNullMessage(INFO, message, null);
}
public void warning(String message, Throwable t) {
writeFixNullMessage( WARNING, message, t);
}
public void warning(String message, Throwable t) {
writeFixNullMessage(WARNING, message, t);
}
public void warning(String message) {
writeFixNullMessage( WARNING, message, null);
}
public void warning(String message) {
writeFixNullMessage(WARNING, message, null);
}
public boolean isDebugEnabled() {
return true;
}
public boolean isDebugEnabled() {
return true;
}
public boolean isErrorEnabled() {
return true;
}
public boolean isInfoEnabled() {
return true;
}
public boolean isWarningEnabled() {
return true;
}
public boolean isErrorEnabled() {
return true;
}
public boolean isInfoEnabled() {
return true;
}
public boolean isWarningEnabled() {
return true;
}
}

View File

@ -13,11 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.logging;
public class ConsoleLoggerFactory implements LoggerFactory {
public LoggerInterface getLogger(String category) {
return new StreamLogger( category, System.out);
}
public LoggerInterface getLogger(String category) {
return new StreamLogger(category, System.out);
}
}

View File

@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.logging;
public interface LoggerFactory {
public LoggerInterface getLogger( String category);
public LoggerInterface getLogger(String category);
}

View File

@ -13,31 +13,40 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.logging;
public interface LoggerInterface {
public static final String ERROR = "ERROR";
public static final String WARNING = "WARNING";
public static final String INFO = "INFO";
public static final String DEBUG = "DEBUG";
public static final String ERROR = "ERROR";
public static final String WARNING = "WARNING";
public static final String INFO = "INFO";
public static final String DEBUG = "DEBUG";
public boolean isErrorEnabled();
public void error( String message);
public void error( String message, Throwable t);
public boolean isErrorEnabled();
public void error(String message);
public void error(String message, Throwable t);
public boolean isWarningEnabled();
public void warning( String message);
public void warning( String message, Throwable t);
public boolean isWarningEnabled();
public boolean isInfoEnabled();
public void info( String message);
public void info( String message, Throwable t);
public void warning(String message);
public boolean isDebugEnabled();
public void debug( String message);
public void debug( String message, Throwable t);
public void warning(String message, Throwable t);
public boolean isInfoEnabled();
public void info(String message);
public void info(String message, Throwable t);
public boolean isDebugEnabled();
public void debug(String message);
public void debug(String message, Throwable t);
}

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.logging;
import java.util.Map;
@ -20,21 +21,21 @@ import java.util.TreeMap;
public class LoggerManager {
static LoggerFactory factory = new NullLoggerFactory();
static LoggerFactory factory = new NullLoggerFactory();
static Map<String,LoggerInterface> loggers = new TreeMap<String,LoggerInterface>();
static Map<String, LoggerInterface> loggers = new TreeMap<String, LoggerInterface>();
public static void setLoggerFactory( LoggerFactory f) {
factory = f;
}
public static void setLoggerFactory(LoggerFactory f) {
factory = f;
}
public static LoggerInterface getLogger(String category) {
public static LoggerInterface getLogger(String category) {
LoggerInterface logger = loggers.get( category);
if (logger == null) {
logger = factory.getLogger(category);
loggers.put( category, logger);
}
return logger;
}
LoggerInterface logger = loggers.get(category);
if (logger == null) {
logger = factory.getLogger(category);
loggers.put(category, logger);
}
return logger;
}
}

View File

@ -13,57 +13,58 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.logging;
public class NullLoggerFactory implements LoggerFactory {
static LoggerInterface logger = new LoggerInterface() {
static LoggerInterface logger = new LoggerInterface() {
public void debug(String message) {
}
public void debug(String message) {
}
public void debug(String message, Throwable t) {
}
public void debug(String message, Throwable t) {
}
public void error(String message) {
}
public void error(String message) {
}
public void error(String message, Throwable t) {
}
public void error(String message, Throwable t) {
}
public void info(String message) {
}
public void info(String message) {
}
public void info(String message, Throwable t) {
}
public void info(String message, Throwable t) {
}
public boolean isDebugEnabled() {
return false;
}
public boolean isDebugEnabled() {
return false;
}
public boolean isErrorEnabled() {
return false;
}
public boolean isErrorEnabled() {
return false;
}
public boolean isInfoEnabled() {
return false;
}
public boolean isInfoEnabled() {
return false;
}
public boolean isWarningEnabled() {
return false;
}
public boolean isWarningEnabled() {
return false;
}
public void warning(String message) {
}
public void warning(String message) {
}
public void warning(String message, Throwable t) {
}
public void warning(String message, Throwable t) {
}
};
};
public LoggerInterface getLogger(String category) {
return logger;
}
public LoggerInterface getLogger(String category) {
return logger;
}
}

View File

@ -13,24 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.logging;
import java.io.PrintStream;
public class StreamLogger extends AbstractLogger {
PrintStream out;
PrintStream out;
public StreamLogger( String category, PrintStream out)
{
super( category);
this.out = out;
}
public StreamLogger(String category, PrintStream out) {
super(category);
this.out = out;
}
@Override
protected void write(String level, String message, Throwable t) {
out.print( format( level, message));
if (t != null) t.printStackTrace(out);
}
@Override
protected void write(String level, String message, Throwable t) {
out.print(format(level, message));
if (t != null) t.printStackTrace(out);
}
}

View File

@ -1,14 +1,15 @@
package kellinwood.security.zipsigner;
public class AutoKeyException extends RuntimeException {
private static final long serialVersionUID = 1L;
public AutoKeyException( String message) {
public AutoKeyException(String message) {
super(message);
}
public AutoKeyException( String message, Throwable cause) {
public AutoKeyException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -15,15 +15,16 @@
*/
package kellinwood.security.zipsigner;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
/*
* This class provides Base64 encoding services using one of several possible
* implementations available elsewhere in the classpath. Supported implementations
@ -55,16 +56,16 @@ public class Base64 {
Class<Object> clazz;
logger = LoggerManager.getLogger( Base64.class.getName());
logger = LoggerManager.getLogger(Base64.class.getName());
try {
clazz = (Class<Object>) Class.forName("android.util.Base64");
// Looking for encode( byte[] input, int flags)
aEncodeMethod = clazz.getMethod("encode", byte[].class, Integer.TYPE);
aDecodeMethod = clazz.getMethod("decode", byte[].class, Integer.TYPE);
logger.info( clazz.getName() + " is available.");
}
catch (ClassNotFoundException x) {} // Ignore
logger.info(clazz.getName() + " is available.");
} catch (ClassNotFoundException x) {
} // Ignore
catch (Exception x) {
logger.error("Failed to initialize use of android.util.Base64", x);
}
@ -74,12 +75,12 @@ public class Base64 {
bEncoder = clazz.newInstance();
// Looking for encode( byte[] input, int offset, int length, OutputStream output)
bEncodeMethod = clazz.getMethod("encode", byte[].class, Integer.TYPE, Integer.TYPE, OutputStream.class);
logger.info( clazz.getName() + " is available.");
logger.info(clazz.getName() + " is available.");
// Looking for decode( byte[] input, int offset, int length, OutputStream output)
bDecodeMethod = clazz.getMethod("decode", byte[].class, Integer.TYPE, Integer.TYPE, OutputStream.class);
}
catch (ClassNotFoundException x) {} // Ignore
} catch (ClassNotFoundException x) {
} // Ignore
catch (Exception x) {
logger.error("Failed to initialize use of org.bouncycastle.util.encoders.Base64Encoder", x);
}
@ -89,42 +90,38 @@ public class Base64 {
}
public static String encode( byte[] data) {
public static String encode(byte[] data) {
try {
if (aEncodeMethod != null) {
// Invoking a static method call, using null for the instance value
byte[] encodedBytes = (byte[])aEncodeMethod.invoke(null, data, 2);
return new String( encodedBytes);
}
else if (bEncodeMethod != null) {
byte[] encodedBytes = (byte[]) aEncodeMethod.invoke(null, data, 2);
return new String(encodedBytes);
} else if (bEncodeMethod != null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bEncodeMethod.invoke(bEncoder, data, 0, data.length, baos);
return new String( baos.toByteArray());
return new String(baos.toByteArray());
}
}
catch (Exception x) {
throw new IllegalStateException( x.getClass().getName() + ": " + x.getMessage());
} catch (Exception x) {
throw new IllegalStateException(x.getClass().getName() + ": " + x.getMessage());
}
throw new IllegalStateException("No base64 encoder implementation is available.");
}
public static byte[] decode( byte[] data) {
public static byte[] decode(byte[] data) {
try {
if (aDecodeMethod != null) {
// Invoking a static method call, using null for the instance value
byte[] decodedBytes = (byte[])aDecodeMethod.invoke(null, data, 2);
byte[] decodedBytes = (byte[]) aDecodeMethod.invoke(null, data, 2);
return decodedBytes;
}
else if (bDecodeMethod != null) {
} else if (bDecodeMethod != null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bDecodeMethod.invoke(bEncoder, data, 0, data.length, baos);
return baos.toByteArray();
}
}
catch (Exception x) {
throw new IllegalStateException( x.getClass().getName() + ": " + x.getMessage());
} catch (Exception x) {
throw new IllegalStateException(x.getClass().getName() + ": " + x.getMessage());
}

View File

@ -1,3 +1,4 @@
package kellinwood.security.zipsigner;
/**

View File

@ -15,58 +15,58 @@
*/
package kellinwood.security.zipsigner;
import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/** Produces the classic hex dump with an address column, hex data
/**
* Produces the classic hex dump with an address column, hex data
* section (16 bytes per row) and right-column printable character dislpay.
*/
public class HexDumpEncoder
{
public class HexDumpEncoder {
static HexEncoder encoder = new HexEncoder();
public static String encode( byte[] data) {
public static String encode(byte[] data) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
encoder.encode( data, 0, data.length, baos);
encoder.encode(data, 0, data.length, baos);
byte[] hex = baos.toByteArray();
StringBuilder hexDumpOut = new StringBuilder();
for (int i = 0; i < hex.length; i += 32) {
int max = Math.min(i+32, hex.length);
int max = Math.min(i + 32, hex.length);
StringBuilder hexOut = new StringBuilder();
StringBuilder chrOut = new StringBuilder();
hexOut.append( String.format("%08x: ", (i/2)));
hexOut.append(String.format("%08x: ", (i / 2)));
for (int j = i; j < max; j+= 2) {
hexOut.append( Character.valueOf( (char)hex[j]));
hexOut.append( Character.valueOf( (char)hex[j+1]));
if ((j+2) % 4 == 0) hexOut.append( ' ');
for (int j = i; j < max; j += 2) {
hexOut.append(Character.valueOf((char) hex[j]));
hexOut.append(Character.valueOf((char) hex[j + 1]));
if ((j + 2) % 4 == 0) hexOut.append(' ');
int dataChar = data[j/2];
if (dataChar >= 32 && dataChar < 127) chrOut.append( Character.valueOf( (char)dataChar));
else chrOut.append( '.');
int dataChar = data[j / 2];
if (dataChar >= 32 && dataChar < 127) chrOut.append(Character.valueOf((char) dataChar));
else chrOut.append('.');
}
hexDumpOut.append( hexOut.toString());
hexDumpOut.append(hexOut.toString());
for (int k = hexOut.length(); k < 50; k++) hexDumpOut.append(' ');
hexDumpOut.append( " ");
hexDumpOut.append( chrOut);
hexDumpOut.append(" ");
hexDumpOut.append(chrOut);
hexDumpOut.append("\n");
}
return hexDumpOut.toString();
}
catch (IOException x) {
throw new IllegalStateException( x.getClass().getName() + ": " + x.getMessage());
} catch (IOException x) {
throw new IllegalStateException(x.getClass().getName() + ": " + x.getMessage());
}
}

View File

@ -1,3 +1,4 @@
package kellinwood.security.zipsigner;
/*
@ -32,24 +33,21 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import java.io.IOException;
import java.io.OutputStream;
public class HexEncoder
{
public class HexEncoder {
protected final byte[] encodingTable =
{
(byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7',
(byte)'8', (byte)'9', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f'
};
{
(byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7',
(byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f'
};
/*
* set up the decoding table.
*/
protected final byte[] decodingTable = new byte[128];
protected void initialiseDecodingTable()
{
for (int i = 0; i < encodingTable.length; i++)
{
decodingTable[encodingTable[i]] = (byte)i;
protected void initialiseDecodingTable() {
for (int i = 0; i < encodingTable.length; i++) {
decodingTable[encodingTable[i]] = (byte) i;
}
decodingTable['A'] = decodingTable['a'];
@ -60,8 +58,7 @@ public class HexEncoder
decodingTable['F'] = decodingTable['f'];
}
public HexEncoder()
{
public HexEncoder() {
initialiseDecodingTable();
}
@ -71,15 +68,13 @@ public class HexEncoder
* @return the number of bytes produced.
*/
public int encode(
byte[] data,
int off,
int length,
OutputStream out)
throws IOException
{
for (int i = off; i < (off + length); i++)
{
int v = data[i] & 0xff;
byte[] data,
int off,
int length,
OutputStream out)
throws IOException {
for (int i = off; i < (off + length); i++) {
int v = data[i] & 0xff;
out.write(encodingTable[(v >>> 4)]);
out.write(encodingTable[v & 0xf]);
@ -89,9 +84,8 @@ public class HexEncoder
}
private boolean ignore(
char c)
{
return (c == '\n' || c =='\r' || c == '\t' || c == ' ');
char c) {
return (c == '\n' || c == '\r' || c == '\t' || c == ' ');
}
/**
@ -101,21 +95,18 @@ public class HexEncoder
* @return the number of bytes produced.
*/
public int decode(
byte[] data,
int off,
int length,
OutputStream out)
throws IOException
{
byte b1, b2;
int outLen = 0;
byte[] data,
int off,
int length,
OutputStream out)
throws IOException {
byte b1, b2;
int outLen = 0;
int end = off + length;
int end = off + length;
while (end > off)
{
if (!ignore((char)data[end - 1]))
{
while (end > off) {
if (!ignore((char) data[end - 1])) {
break;
}
@ -123,17 +114,14 @@ public class HexEncoder
}
int i = off;
while (i < end)
{
while (i < end && ignore((char)data[i]))
{
while (i < end) {
while (i < end && ignore((char) data[i])) {
i++;
}
b1 = decodingTable[data[i++]];
while (i < end && ignore((char)data[i]))
{
while (i < end && ignore((char) data[i])) {
i++;
}
@ -154,19 +142,16 @@ public class HexEncoder
* @return the number of bytes produced.
*/
public int decode(
String data,
OutputStream out)
throws IOException
{
byte b1, b2;
int length = 0;
String data,
OutputStream out)
throws IOException {
byte b1, b2;
int length = 0;
int end = data.length();
int end = data.length();
while (end > 0)
{
if (!ignore(data.charAt(end - 1)))
{
while (end > 0) {
if (!ignore(data.charAt(end - 1))) {
break;
}
@ -174,17 +159,14 @@ public class HexEncoder
}
int i = 0;
while (i < end)
{
while (i < end && ignore(data.charAt(i)))
{
while (i < end) {
while (i < end && ignore(data.charAt(i))) {
i++;
}
b1 = decodingTable[data.charAt(i++)];
while (i < end && ignore(data.charAt(i)))
{
while (i < end && ignore(data.charAt(i))) {
i++;
}

View File

@ -1,5 +1,3 @@
/*
* Copyright (C) 2010 Ken Ellinwood
*
@ -15,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.security.zipsigner;
import java.security.PrivateKey;
@ -38,16 +37,14 @@ public class KeySet {
public KeySet() {
}
public KeySet( String name, X509Certificate publicKey, PrivateKey privateKey, byte[] sigBlockTemplate)
{
public KeySet(String name, X509Certificate publicKey, PrivateKey privateKey, byte[] sigBlockTemplate) {
this.name = name;
this.publicKey = publicKey;
this.privateKey = privateKey;
this.sigBlockTemplate = sigBlockTemplate;
}
public KeySet( String name, X509Certificate publicKey, PrivateKey privateKey, String signatureAlgorithm, byte[] sigBlockTemplate)
{
public KeySet(String name, X509Certificate publicKey, PrivateKey privateKey, String signatureAlgorithm, byte[] sigBlockTemplate) {
this.name = name;
this.publicKey = publicKey;
this.privateKey = privateKey;

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.security.zipsigner;
public class ProgressEvent {
@ -27,18 +28,23 @@ public class ProgressEvent {
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public int getPercentDone() {
return percentDone;
}
public void setPercentDone(int percentDone) {
this.percentDone = percentDone;
}
public int getPriority() {
return priority;
}
public void setPriority(int priority) {
this.priority = priority;
}

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.security.zipsigner;
import java.util.ArrayList;
@ -23,8 +24,7 @@ public class ProgressHelper {
private int progressCurrentItem = 0;
private ProgressEvent progressEvent = new ProgressEvent();
public void initProgress()
{
public void initProgress() {
progressTotalItems = 10000;
progressCurrentItem = 0;
}
@ -45,7 +45,7 @@ public class ProgressHelper {
this.progressCurrentItem = progressCurrentItem;
}
public void progress( int priority, String message) {
public void progress(int priority, String message) {
progressCurrentItem += 1;
@ -58,24 +58,22 @@ public class ProgressHelper {
progressEvent.setMessage(message);
progressEvent.setPercentDone(percentDone);
progressEvent.setPriority(priority);
listener.onProgress( progressEvent);
listener.onProgress(progressEvent);
}
}
private ArrayList<ProgressListener> listeners = new ArrayList<ProgressListener>();
@SuppressWarnings("unchecked")
public synchronized void addProgressListener( ProgressListener l)
{
ArrayList<ProgressListener> list = (ArrayList<ProgressListener>)listeners.clone();
public synchronized void addProgressListener(ProgressListener l) {
ArrayList<ProgressListener> list = (ArrayList<ProgressListener>) listeners.clone();
list.add(l);
listeners = list;
}
@SuppressWarnings("unchecked")
public synchronized void removeProgressListener( ProgressListener l)
{
ArrayList<ProgressListener> list = (ArrayList<ProgressListener>)listeners.clone();
public synchronized void removeProgressListener(ProgressListener l) {
ArrayList<ProgressListener> list = (ArrayList<ProgressListener>) listeners.clone();
list.remove(l);
listeners = list;
}

View File

@ -13,12 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.security.zipsigner;
public interface ProgressListener {
/** Called to notify the listener that progress has been made during
the zip signing operation.
/**
* Called to notify the listener that progress has been made during
* the zip signing operation.
*/
public void onProgress( ProgressEvent event);
public void onProgress(ProgressEvent event);
}

View File

@ -1,3 +1,4 @@
package kellinwood.security.zipsigner;
/**
@ -14,7 +15,9 @@ public interface ResourceAdapter {
GENERATING_SIGNATURE_FILE,
GENERATING_SIGNATURE_BLOCK,
COPYING_ZIP_ENTRY
};
}
public String getString( Item item, Object... args);
;
public String getString(Item item, Object... args);
}

View File

@ -13,59 +13,56 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.security.zipsigner;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.PrivateKey;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
@SuppressWarnings("restriction")
public class ZipSignature {
byte[] beforeAlgorithmIdBytes = { 0x30, 0x21 };
byte[] beforeAlgorithmIdBytes = {0x30, 0x21};
// byte[] algorithmIdBytes;
// algorithmIdBytes = sun.security.x509.AlgorithmId.get("SHA1").encode();
byte[] algorithmIdBytes = {0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00 };
byte[] algorithmIdBytes = {0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00};
byte[] afterAlgorithmIdBytes = { 0x04, 0x14 };
byte[] afterAlgorithmIdBytes = {0x04, 0x14};
Cipher cipher;
MessageDigest md;
public ZipSignature() throws IOException, GeneralSecurityException
{
public ZipSignature() throws IOException, GeneralSecurityException {
md = MessageDigest.getInstance("SHA1");
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
}
public void initSign( PrivateKey privateKey) throws InvalidKeyException
{
public void initSign(PrivateKey privateKey) throws InvalidKeyException {
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
}
public void update( byte[] data) {
md.update( data);
public void update(byte[] data) {
md.update(data);
}
public void update( byte[] data, int offset, int count) {
md.update( data, offset, count);
public void update(byte[] data, int offset, int count) {
md.update(data, offset, count);
}
public byte[] sign() throws BadPaddingException, IllegalBlockSizeException
{
cipher.update( beforeAlgorithmIdBytes);
cipher.update( algorithmIdBytes);
cipher.update( afterAlgorithmIdBytes);
cipher.update( md.digest());
public byte[] sign() throws BadPaddingException, IllegalBlockSizeException {
cipher.update(beforeAlgorithmIdBytes);
cipher.update(algorithmIdBytes);
cipher.update(afterAlgorithmIdBytes);
cipher.update(md.digest());
return cipher.doFinal();
}
}

View File

@ -26,6 +26,7 @@
* using signature block template files.
*/
package kellinwood.security.zipsigner;
import kellinwood.logging.LoggerInterface;
@ -38,17 +39,40 @@ import javax.crypto.Cipher;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.io.*;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.security.*;
import java.security.DigestOutputStream;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.TreeMap;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
@ -58,11 +82,10 @@ import java.util.regex.Pattern;
* This is a modified copy of com.android.signapk.SignApk.java. It provides an
* API to sign JAR files (including APKs and Zip/OTA updates) in
* a way compatible with the mincrypt verifier, using SHA1 and RSA keys.
*
* <p>
* Please see the README.txt file in the root of this project for usage instructions.
*/
public class ZipSigner
{
public class ZipSigner {
private boolean canceled = false;
@ -76,13 +99,13 @@ public class ZipSigner
// Files matching this pattern are not copied to the output.
private static Pattern stripPattern =
Pattern.compile("^META-INF/(.*)[.](SF|RSA|DSA)$");
Pattern.compile("^META-INF/(.*)[.](SF|RSA|DSA)$");
Map<String,KeySet> loadedKeys = new HashMap<String,KeySet>();
Map<String, KeySet> loadedKeys = new HashMap<String, KeySet>();
KeySet keySet = null;
public static LoggerInterface getLogger() {
if (log == null) log = LoggerManager.getLogger( ZipSigner.class.getName());
if (log == null) log = LoggerManager.getLogger(ZipSigner.class.getName());
return log;
}
@ -94,21 +117,20 @@ public class ZipSigner
// Allowable key modes.
public static final String[] SUPPORTED_KEY_MODES =
new String[] { MODE_AUTO_TESTKEY, MODE_AUTO, MODE_AUTO_NONE, "media", "platform", "shared", KEY_TESTKEY, KEY_NONE};
new String[]{MODE_AUTO_TESTKEY, MODE_AUTO, MODE_AUTO_NONE, "media", "platform", "shared", KEY_TESTKEY, KEY_NONE};
String keymode = KEY_TESTKEY; // backwards compatible with versions that only signed with this key
Map<String,String> autoKeyDetect = new HashMap<String,String>();
Map<String, String> autoKeyDetect = new HashMap<String, String>();
AutoKeyObservable autoKeyObservable = new AutoKeyObservable();
public ZipSigner() throws ClassNotFoundException, IllegalAccessException, InstantiationException
{
public ZipSigner() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
// MD5 of the first 1458 bytes of the signature block generated by the key, mapped to the key name
autoKeyDetect.put( "aa9852bc5a53272ac8031d49b65e4b0e", "media");
autoKeyDetect.put( "e60418c4b638f20d0721e115674ca11f", "platform");
autoKeyDetect.put( "3e24e49741b60c215c010dc6048fca7d", "shared");
autoKeyDetect.put( "dab2cead827ef5313f28e22b6fa8479f", "testkey");
autoKeyDetect.put("aa9852bc5a53272ac8031d49b65e4b0e", "media");
autoKeyDetect.put("e60418c4b638f20d0721e115674ca11f", "platform");
autoKeyDetect.put("3e24e49741b60c215c010dc6048fca7d", "shared");
autoKeyDetect.put("dab2cead827ef5313f28e22b6fa8479f", "testkey");
}
@ -121,7 +143,7 @@ public class ZipSigner
}
// when the key mode is automatic, the observers are called when the key is determined
public void addAutoKeyObserver( Observer o) {
public void addAutoKeyObserver(Observer o) {
autoKeyObservable.addObserver(o);
}
@ -129,16 +151,14 @@ public class ZipSigner
return keymode;
}
public void setKeymode(String km) throws IOException, GeneralSecurityException
{
public void setKeymode(String km) throws IOException, GeneralSecurityException {
if (getLogger().isDebugEnabled()) getLogger().debug("setKeymode: " + km);
keymode = km;
if (keymode.startsWith(MODE_AUTO)) {
keySet = null;
}
else {
} else {
progressHelper.initProgress();
loadKeys( keymode);
loadKeys(keymode);
}
}
@ -147,9 +167,8 @@ public class ZipSigner
}
protected String autoDetectKey( String mode, Map<String,ZioEntry> zioEntries)
throws NoSuchAlgorithmException, IOException
{
protected String autoDetectKey(String mode, Map<String, ZioEntry> zioEntries)
throws NoSuchAlgorithmException, IOException {
boolean debug = getLogger().isDebugEnabled();
if (!mode.startsWith(MODE_AUTO)) return mode;
@ -158,7 +177,7 @@ public class ZipSigner
// Auto-determine which keys to use
String keyName = null;
// Start by finding the signature block file in the input.
for (Map.Entry<String,ZioEntry> entry : zioEntries.entrySet()) {
for (Map.Entry<String, ZioEntry> entry : zioEntries.entrySet()) {
String entryName = entry.getKey();
if (entryName.startsWith("META-INF/") && entryName.endsWith(".RSA")) {
@ -167,18 +186,18 @@ public class ZipSigner
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] entryData = entry.getValue().getData();
if (entryData.length < 1458) break; // sig block too short to be a supported key
md5.update( entryData, 0, 1458);
md5.update(entryData, 0, 1458);
byte[] rawDigest = md5.digest();
// Create the hex representation of the digest value
StringBuilder builder = new StringBuilder();
for( byte b : rawDigest) {
builder.append( String.format("%02x", b));
for (byte b : rawDigest) {
builder.append(String.format("%02x", b));
}
String md5String = builder.toString();
// Lookup the key name
keyName = autoKeyDetect.get( md5String);
keyName = autoKeyDetect.get(md5String);
if (debug) {
@ -192,13 +211,12 @@ public class ZipSigner
}
}
if (mode.equals( MODE_AUTO_TESTKEY)) {
if (mode.equals(MODE_AUTO_TESTKEY)) {
// in auto-testkey mode, fallback to the testkey if it couldn't be determined
if (debug) getLogger().debug("Falling back to key="+ keyName);
if (debug) getLogger().debug("Falling back to key=" + keyName);
return KEY_TESTKEY;
}
else if (mode.equals(MODE_AUTO_NONE)) {
} else if (mode.equals(MODE_AUTO_NONE)) {
// in auto-node mode, simply copy the input to the output when the key can't be determined.
if (debug) getLogger().debug("Unable to determine key, returning: " + KEY_NONE);
return KEY_NONE;
@ -212,44 +230,41 @@ public class ZipSigner
}
// Loads one of the built-in keys (media, platform, shared, testkey)
public void loadKeys( String name)
throws IOException, GeneralSecurityException
{
public void loadKeys(String name)
throws IOException, GeneralSecurityException {
keySet = loadedKeys.get(name);
if (keySet != null) return;
keySet = new KeySet();
keySet.setName(name);
loadedKeys.put( name, keySet);
loadedKeys.put(name, keySet);
if (KEY_NONE.equals(name)) return;
issueLoadingCertAndKeysProgressEvent();
// load the private key
URL privateKeyUrl = getClass().getResource("/keys/"+name+".pk8");
URL privateKeyUrl = getClass().getResource("/keys/" + name + ".pk8");
keySet.setPrivateKey(readPrivateKey(privateKeyUrl, null));
// load the certificate
URL publicKeyUrl = getClass().getResource("/keys/"+name+".x509.pem");
URL publicKeyUrl = getClass().getResource("/keys/" + name + ".x509.pem");
keySet.setPublicKey(readPublicKey(publicKeyUrl));
// load the signature block template
URL sigBlockTemplateUrl = getClass().getResource("/keys/"+name+".sbt");
URL sigBlockTemplateUrl = getClass().getResource("/keys/" + name + ".sbt");
if (sigBlockTemplateUrl != null) {
keySet.setSigBlockTemplate(readContentAsBytes(sigBlockTemplateUrl));
}
}
public void setKeys( String name, X509Certificate publicKey, PrivateKey privateKey, byte[] signatureBlockTemplate)
{
keySet = new KeySet( name, publicKey, privateKey, signatureBlockTemplate);
public void setKeys(String name, X509Certificate publicKey, PrivateKey privateKey, byte[] signatureBlockTemplate) {
keySet = new KeySet(name, publicKey, privateKey, signatureBlockTemplate);
}
public void setKeys( String name, X509Certificate publicKey, PrivateKey privateKey, String signatureAlgorithm, byte[] signatureBlockTemplate)
{
keySet = new KeySet( name, publicKey, privateKey, signatureAlgorithm, signatureBlockTemplate);
public void setKeys(String name, X509Certificate publicKey, PrivateKey privateKey, String signatureAlgorithm, byte[] signatureBlockTemplate) {
keySet = new KeySet(name, publicKey, privateKey, signatureAlgorithm, signatureBlockTemplate);
}
public KeySet getKeySet() {
@ -271,17 +286,16 @@ public class ZipSigner
}
@SuppressWarnings("unchecked")
public void loadProvider( String providerClassName)
throws ClassNotFoundException, IllegalAccessException, InstantiationException
{
public void loadProvider(String providerClassName)
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class providerClass = Class.forName(providerClassName);
Provider provider = (Provider)providerClass.newInstance();
Provider provider = (Provider) providerClass.newInstance();
Security.insertProviderAt(provider, 1);
}
public X509Certificate readPublicKey(URL publicKeyUrl)
throws IOException, GeneralSecurityException {
throws IOException, GeneralSecurityException {
InputStream input = publicKeyUrl.openStream();
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
@ -293,15 +307,15 @@ public class ZipSigner
/**
* Decrypt an encrypted PKCS 8 format private key.
*
* <p>
* Based on ghstark's post on Aug 6, 2006 at
* http://forums.sun.com/thread.jspa?threadID=758133&messageID=4330949
*
* @param encryptedPrivateKey The raw data of the private key
* @param keyPassword the key password
* @param keyPassword the key password
*/
private KeySpec decryptPrivateKey(byte[] encryptedPrivateKey, String keyPassword)
throws GeneralSecurityException {
throws GeneralSecurityException {
EncryptedPrivateKeyInfo epkInfo;
try {
epkInfo = new EncryptedPrivateKeyInfo(encryptedPrivateKey);
@ -326,35 +340,39 @@ public class ZipSigner
}
}
/** Fetch the content at the specified URL and return it as a byte array. */
public byte[] readContentAsBytes( URL contentUrl) throws IOException
{
return readContentAsBytes( contentUrl.openStream());
/**
* Fetch the content at the specified URL and return it as a byte array.
*/
public byte[] readContentAsBytes(URL contentUrl) throws IOException {
return readContentAsBytes(contentUrl.openStream());
}
/** Fetch the content from the given stream and return it as a byte array. */
public byte[] readContentAsBytes( InputStream input) throws IOException
{
/**
* Fetch the content from the given stream and return it as a byte array.
*/
public byte[] readContentAsBytes(InputStream input) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int numRead = input.read( buffer);
int numRead = input.read(buffer);
while (numRead != -1) {
baos.write( buffer, 0, numRead);
numRead = input.read( buffer);
baos.write(buffer, 0, numRead);
numRead = input.read(buffer);
}
byte[] bytes = baos.toByteArray();
return bytes;
}
/** Read a PKCS 8 format private key. */
/**
* Read a PKCS 8 format private key.
*/
public PrivateKey readPrivateKey(URL privateKeyUrl, String keyPassword)
throws IOException, GeneralSecurityException {
DataInputStream input = new DataInputStream( privateKeyUrl.openStream());
throws IOException, GeneralSecurityException {
DataInputStream input = new DataInputStream(privateKeyUrl.openStream());
try {
byte[] bytes = readContentAsBytes( input);
byte[] bytes = readContentAsBytes(input);
KeySpec spec = decryptPrivateKey(bytes, keyPassword);
if (spec == null) {
@ -371,15 +389,16 @@ public class ZipSigner
}
}
/** Add the SHA1 of every file to the manifest, creating it if necessary. */
private Manifest addDigestsToManifest(Map<String,ZioEntry> entries)
throws IOException, GeneralSecurityException
{
/**
* Add the SHA1 of every file to the manifest, creating it if necessary.
*/
private Manifest addDigestsToManifest(Map<String, ZioEntry> entries)
throws IOException, GeneralSecurityException {
Manifest input = null;
ZioEntry manifestEntry = entries.get(JarFile.MANIFEST_NAME);
if (manifestEntry != null) {
input = new Manifest();
input.read( manifestEntry.getInputStream());
input.read(manifestEntry.getInputStream());
}
Manifest output = new Manifest();
Attributes main = output.getMainAttributes();
@ -400,21 +419,20 @@ public class ZipSigner
// map will be deterministic.
TreeMap<String, ZioEntry> byName = new TreeMap<String, ZioEntry>();
byName.putAll( entries);
byName.putAll(entries);
boolean debug = getLogger().isDebugEnabled();
if (debug) getLogger().debug("Manifest entries:");
for (ZioEntry entry: byName.values()) {
for (ZioEntry entry : byName.values()) {
if (canceled) break;
String name = entry.getName();
if (debug) getLogger().debug(name);
if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) &&
!name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) &&
(stripPattern == null ||
!stripPattern.matcher(name).matches()))
{
!stripPattern.matcher(name).matches())) {
progressHelper.progress( ProgressEvent.PRORITY_NORMAL, resourceAdapter.getString(ResourceAdapter.Item.GENERATING_MANIFEST));
progressHelper.progress(ProgressEvent.PRORITY_NORMAL, resourceAdapter.getString(ResourceAdapter.Item.GENERATING_MANIFEST));
InputStream data = entry.getInputStream();
while ((num = data.read(buffer)) > 0) {
md.update(buffer, 0, num);
@ -423,7 +441,7 @@ public class ZipSigner
Attributes attr = null;
if (input != null) {
java.util.jar.Attributes inAttr = input.getAttributes(name);
if (inAttr != null) attr = new Attributes( inAttr);
if (inAttr != null) attr = new Attributes(inAttr);
}
if (attr == null) attr = new Attributes();
attr.putValue("SHA1-Digest", Base64.encode(md.digest()));
@ -435,11 +453,13 @@ public class ZipSigner
}
/** Write the signature file to the given output stream. */
/**
* Write the signature file to the given output stream.
*/
private void generateSignatureFile(Manifest manifest, OutputStream out)
throws IOException, GeneralSecurityException {
out.write( ("Signature-Version: 1.0\r\n").getBytes());
out.write( ("Created-By: 1.0 (Android SignApk)\r\n").getBytes());
throws IOException, GeneralSecurityException {
out.write(("Signature-Version: 1.0\r\n").getBytes());
out.write(("Created-By: 1.0 (Android SignApk)\r\n").getBytes());
// BASE64Encoder base64 = new BASE64Encoder();
@ -452,32 +472,33 @@ public class ZipSigner
manifest.write(print);
print.flush();
out.write( ("SHA1-Digest-Manifest: "+ Base64.encode(md.digest()) + "\r\n\r\n").getBytes());
out.write(("SHA1-Digest-Manifest: " + Base64.encode(md.digest()) + "\r\n\r\n").getBytes());
Map<String, Attributes> entries = manifest.getEntries();
for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
if (canceled) break;
progressHelper.progress( ProgressEvent.PRORITY_NORMAL, resourceAdapter.getString(ResourceAdapter.Item.GENERATING_SIGNATURE_FILE));
progressHelper.progress(ProgressEvent.PRORITY_NORMAL, resourceAdapter.getString(ResourceAdapter.Item.GENERATING_SIGNATURE_FILE));
// Digest of the manifest stanza for this entry.
String nameEntry = "Name: " + entry.getKey() + "\r\n";
print.print( nameEntry);
print.print(nameEntry);
for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {
print.print(att.getKey() + ": " + att.getValue() + "\r\n");
}
print.print("\r\n");
print.flush();
out.write( nameEntry.getBytes());
out.write( ("SHA1-Digest: " + Base64.encode(md.digest()) + "\r\n\r\n").getBytes());
out.write(nameEntry.getBytes());
out.write(("SHA1-Digest: " + Base64.encode(md.digest()) + "\r\n\r\n").getBytes());
}
}
/** Write a .RSA file with a digital signature. */
/**
* Write a .RSA file with a digital signature.
*/
@SuppressWarnings("unchecked")
private void writeSignatureBlock( KeySet keySet, byte[] signatureFileBytes, OutputStream out)
throws IOException, GeneralSecurityException
{
private void writeSignatureBlock(KeySet keySet, byte[] signatureFileBytes, OutputStream out)
throws IOException, GeneralSecurityException {
if (keySet.getSigBlockTemplate() != null) {
// Can't use default Signature on Android. Although it generates a signature that can be verified by jarsigner,
@ -488,35 +509,34 @@ public class ZipSigner
signature.update(signatureFileBytes);
byte[] signatureBytes = signature.sign();
out.write( keySet.getSigBlockTemplate());
out.write( signatureBytes);
out.write(keySet.getSigBlockTemplate());
out.write(signatureBytes);
if (getLogger().isDebugEnabled()) {
MessageDigest md = MessageDigest.getInstance("SHA1");
md.update( signatureFileBytes);
md.update(signatureFileBytes);
byte[] sfDigest = md.digest();
getLogger().debug( "Sig File SHA1: \n" + HexDumpEncoder.encode( sfDigest));
getLogger().debug("Sig File SHA1: \n" + HexDumpEncoder.encode(sfDigest));
getLogger().debug( "Signature: \n" + HexDumpEncoder.encode(signatureBytes));
getLogger().debug("Signature: \n" + HexDumpEncoder.encode(signatureBytes));
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, keySet.getPublicKey());
byte[] tmpData = cipher.doFinal( signatureBytes);
getLogger().debug( "Signature Decrypted: \n" + HexDumpEncoder.encode(tmpData));
byte[] tmpData = cipher.doFinal(signatureBytes);
getLogger().debug("Signature Decrypted: \n" + HexDumpEncoder.encode(tmpData));
}
}
else {
} else {
try {
byte[] sigBlock = null;
// Use reflection to call the optional generator.
Class generatorClass = Class.forName("kellinwood.security.zipsigner.optional.SignatureBlockGenerator");
Method generatorMethod = generatorClass.getMethod("generate", KeySet.class, (new byte[1]).getClass());
sigBlock = (byte[])generatorMethod.invoke(null, keySet, signatureFileBytes);
sigBlock = (byte[]) generatorMethod.invoke(null, keySet, signatureFileBytes);
out.write(sigBlock);
} catch (Exception x) {
throw new RuntimeException(x.getMessage(),x);
throw new RuntimeException(x.getMessage(), x);
}
}
}
@ -527,9 +547,8 @@ public class ZipSigner
* reduce variation in the output file and make incremental OTAs
* more efficient.
*/
private void copyFiles(Manifest manifest, Map<String,ZioEntry> input, ZipOutput output, long timestamp)
throws IOException
{
private void copyFiles(Manifest manifest, Map<String, ZioEntry> input, ZipOutput output, long timestamp)
throws IOException {
Map<String, Attributes> entries = manifest.getEntries();
List<String> names = new ArrayList<String>(entries.keySet());
Collections.sort(names);
@ -548,13 +567,12 @@ public class ZipSigner
/**
* Copy all the files from input to output.
*/
private void copyFiles(Map<String,ZioEntry> input, ZipOutput output)
throws IOException
{
private void copyFiles(Map<String, ZioEntry> input, ZipOutput output)
throws IOException {
int i = 1;
for (ZioEntry inEntry : input.values()) {
if (canceled) break;
progressHelper.progress( ProgressEvent.PRORITY_NORMAL, resourceAdapter.getString(ResourceAdapter.Item.COPYING_ZIP_ENTRY, i, input.size()));
progressHelper.progress(ProgressEvent.PRORITY_NORMAL, resourceAdapter.getString(ResourceAdapter.Item.COPYING_ZIP_ENTRY, i, input.size()));
i += 1;
output.write(inEntry);
}
@ -563,30 +581,28 @@ public class ZipSigner
/**
* @deprecated - use the version that takes the passwords as char[]
*/
public void signZip( URL keystoreURL,
String keystoreType,
String keystorePw,
String certAlias,
String certPw,
String inputZipFilename,
String outputZipFilename)
throws ClassNotFoundException, IllegalAccessException, InstantiationException,
IOException, GeneralSecurityException
{
signZip( keystoreURL, keystoreType, keystorePw.toCharArray(), certAlias, certPw.toCharArray(), "SHA1withRSA", inputZipFilename, outputZipFilename);
public void signZip(URL keystoreURL,
String keystoreType,
String keystorePw,
String certAlias,
String certPw,
String inputZipFilename,
String outputZipFilename)
throws ClassNotFoundException, IllegalAccessException, InstantiationException,
IOException, GeneralSecurityException {
signZip(keystoreURL, keystoreType, keystorePw.toCharArray(), certAlias, certPw.toCharArray(), "SHA1withRSA", inputZipFilename, outputZipFilename);
}
public void signZip( URL keystoreURL,
String keystoreType,
char[] keystorePw,
String certAlias,
char[] certPw,
String signatureAlgorithm,
String inputZipFilename,
String outputZipFilename)
throws ClassNotFoundException, IllegalAccessException, InstantiationException,
IOException, GeneralSecurityException
{
public void signZip(URL keystoreURL,
String keystoreType,
char[] keystorePw,
String certAlias,
char[] certPw,
String signatureAlgorithm,
String inputZipFilename,
String outputZipFilename)
throws ClassNotFoundException, IllegalAccessException, InstantiationException,
IOException, GeneralSecurityException {
InputStream keystoreStream = null;
@ -598,73 +614,69 @@ public class ZipSigner
keystoreStream = keystoreURL.openStream();
keystore.load(keystoreStream, keystorePw);
Certificate cert = keystore.getCertificate(certAlias);
X509Certificate publicKey = (X509Certificate)cert;
X509Certificate publicKey = (X509Certificate) cert;
Key key = keystore.getKey(certAlias, certPw);
PrivateKey privateKey = (PrivateKey)key;
PrivateKey privateKey = (PrivateKey) key;
setKeys( "custom", publicKey, privateKey, signatureAlgorithm, null);
setKeys("custom", publicKey, privateKey, signatureAlgorithm, null);
signZip( inputZipFilename, outputZipFilename);
}
finally {
signZip(inputZipFilename, outputZipFilename);
} finally {
if (keystoreStream != null) keystoreStream.close();
}
}
/** Sign the input with the default test key and certificate.
* Save result to output file.
/**
* Sign the input with the default test key and certificate.
* Save result to output file.
*/
public void signZip( Map<String,ZioEntry> zioEntries, String outputZipFilename)
throws IOException, GeneralSecurityException
{
public void signZip(Map<String, ZioEntry> zioEntries, String outputZipFilename)
throws IOException, GeneralSecurityException {
progressHelper.initProgress();
signZip( zioEntries, new FileOutputStream(outputZipFilename), outputZipFilename);
signZip(zioEntries, new FileOutputStream(outputZipFilename), outputZipFilename);
}
/** Sign the file using the given public key cert, private key,
* and signature block template. The signature block template
* parameter may be null, but if so
* android-sun-jarsign-support.jar must be in the classpath.
/**
* Sign the file using the given public key cert, private key,
* and signature block template. The signature block template
* parameter may be null, but if so
* android-sun-jarsign-support.jar must be in the classpath.
*/
public void signZip( String inputZipFilename, String outputZipFilename)
throws IOException, GeneralSecurityException
{
File inFile = new File( inputZipFilename).getCanonicalFile();
File outFile = new File( outputZipFilename).getCanonicalFile();
public void signZip(String inputZipFilename, String outputZipFilename)
throws IOException, GeneralSecurityException {
File inFile = new File(inputZipFilename).getCanonicalFile();
File outFile = new File(outputZipFilename).getCanonicalFile();
if (inFile.equals(outFile)) {
throw new IllegalArgumentException( resourceAdapter.getString(ResourceAdapter.Item.INPUT_SAME_AS_OUTPUT_ERROR));
throw new IllegalArgumentException(resourceAdapter.getString(ResourceAdapter.Item.INPUT_SAME_AS_OUTPUT_ERROR));
}
progressHelper.initProgress();
progressHelper.progress( ProgressEvent.PRORITY_IMPORTANT, resourceAdapter.getString(ResourceAdapter.Item.PARSING_CENTRAL_DIRECTORY));
progressHelper.progress(ProgressEvent.PRORITY_IMPORTANT, resourceAdapter.getString(ResourceAdapter.Item.PARSING_CENTRAL_DIRECTORY));
ZipInput input = null;
OutputStream outStream = null;
try {
input = ZipInput.read( inputZipFilename);
outStream = new FileOutputStream( outputZipFilename);
input = ZipInput.read(inputZipFilename);
outStream = new FileOutputStream(outputZipFilename);
signZip(input.getEntries(), outStream, outputZipFilename);
}
finally {
if(input != null) input.close();
if(outStream != null) outStream.close();
} finally {
if (input != null) input.close();
if (outStream != null) outStream.close();
}
}
/** Sign the
* and signature block template. The signature block template
* parameter may be null, but if so
* android-sun-jarsign-support.jar must be in the classpath.
/**
* Sign the
* and signature block template. The signature block template
* parameter may be null, but if so
* android-sun-jarsign-support.jar must be in the classpath.
*/
public void signZip( Map<String,ZioEntry> zioEntries, OutputStream outputStream, String outputZipFilename)
throws IOException, GeneralSecurityException
{
boolean debug = getLogger().isDebugEnabled();
public void signZip(Map<String, ZioEntry> zioEntries, OutputStream outputStream, String outputZipFilename)
throws IOException, GeneralSecurityException {
boolean debug = getLogger().isDebugEnabled();
progressHelper.initProgress();
if (keySet == null) {
@ -672,24 +684,23 @@ public class ZipSigner
throw new IllegalStateException("No keys configured for signing the file!");
// Auto-determine which keys to use
String keyName = this.autoDetectKey( keymode, zioEntries);
String keyName = this.autoDetectKey(keymode, zioEntries);
if (keyName == null)
throw new AutoKeyException( resourceAdapter.getString(ResourceAdapter.Item.AUTO_KEY_SELECTION_ERROR, new File( outputZipFilename).getName()));
throw new AutoKeyException(resourceAdapter.getString(ResourceAdapter.Item.AUTO_KEY_SELECTION_ERROR, new File(outputZipFilename).getName()));
autoKeyObservable.notifyObservers(keyName);
loadKeys( keyName);
loadKeys(keyName);
}
ZipOutput zipOutput = null;
try {
zipOutput = new ZipOutput( outputStream);
zipOutput = new ZipOutput(outputStream);
if (KEY_NONE.equals(keySet.getName())) {
progressHelper.setProgressTotalItems(zioEntries.size());
@ -700,13 +711,12 @@ public class ZipSigner
// Calculate total steps to complete for accurate progress percentages.
int progressTotalItems = 0;
for (ZioEntry entry: zioEntries.values()) {
for (ZioEntry entry : zioEntries.values()) {
String name = entry.getName();
if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) &&
!name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) &&
(stripPattern == null ||
!stripPattern.matcher(name).matches()))
{
!stripPattern.matcher(name).matches())) {
progressTotalItems += 3; // digest for manifest, digest in sig file, copy data
}
}
@ -721,7 +731,7 @@ public class ZipSigner
// progress(ProgressEvent.PRORITY_NORMAL, JarFile.MANIFEST_NAME);
Manifest manifest = addDigestsToManifest(zioEntries);
if (canceled) return;
ZioEntry ze = new ZioEntry( JarFile.MANIFEST_NAME);
ZioEntry ze = new ZioEntry(JarFile.MANIFEST_NAME);
ze.setTime(timestamp);
manifest.write(ze.getOutputStream());
zipOutput.write(ze);
@ -736,51 +746,46 @@ public class ZipSigner
if (canceled) return;
byte[] sfBytes = out.toByteArray();
if (debug) {
getLogger().debug( "Signature File: \n" + new String( sfBytes) + "\n" +
HexDumpEncoder.encode( sfBytes));
getLogger().debug("Signature File: \n" + new String(sfBytes) + "\n" +
HexDumpEncoder.encode(sfBytes));
}
ze.getOutputStream().write(sfBytes);
zipOutput.write(ze);
// CERT.RSA
progressHelper.progress( ProgressEvent.PRORITY_NORMAL, resourceAdapter.getString(ResourceAdapter.Item.GENERATING_SIGNATURE_BLOCK));
progressHelper.progress(ProgressEvent.PRORITY_NORMAL, resourceAdapter.getString(ResourceAdapter.Item.GENERATING_SIGNATURE_BLOCK));
ze = new ZioEntry(CERT_RSA_NAME);
ze.setTime(timestamp);
writeSignatureBlock(keySet, sfBytes, ze.getOutputStream());
zipOutput.write( ze);
zipOutput.write(ze);
if (canceled) return;
// Everything else
copyFiles(manifest, zioEntries, zipOutput, timestamp);
if (canceled) return;
}
finally {
} finally {
if (zipOutput != null) zipOutput.close();
if (canceled) {
try {
if (outputZipFilename != null) new File( outputZipFilename).delete();
}
catch (Throwable t) {
getLogger().warning( t.getClass().getName() + ":" + t.getMessage());
if (outputZipFilename != null) new File(outputZipFilename).delete();
} catch (Throwable t) {
getLogger().warning(t.getClass().getName() + ":" + t.getMessage());
}
}
}
}
public void addProgressListener( ProgressListener l)
{
public void addProgressListener(ProgressListener l) {
progressHelper.addProgressListener(l);
}
public synchronized void removeProgressListener( ProgressListener l)
{
public synchronized void removeProgressListener(ProgressListener l) {
progressHelper.removeProgressListener(l);
}
public static class AutoKeyObservable extends Observable
{
public static class AutoKeyObservable extends Observable {
@Override
public void notifyObservers(Object arg) {
super.setChanged();

View File

@ -1,3 +1,4 @@
package kellinwood.security.zipsigner.optional;
import kellinwood.security.zipsigner.KeySet;
@ -7,7 +8,10 @@ import org.bouncycastle.x509.X509V3CertificateGenerator;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Date;
@ -17,84 +21,83 @@ import java.util.Date;
*/
public class CertCreator {
/** Creates a new keystore and self-signed key. The key will have the same password as the key, and will be
* RSA 2048, with the cert signed using SHA1withRSA. The certificate will have a validity of
* 30 years).
/**
* Creates a new keystore and self-signed key. The key will have the same password as the key, and will be
* RSA 2048, with the cert signed using SHA1withRSA. The certificate will have a validity of
* 30 years).
*
* @param storePath - pathname of the new keystore file
* @param password - keystore and key password
* @param keyName - the new key will have this as its alias within the keystore
* @param storePath - pathname of the new keystore file
* @param password - keystore and key password
* @param keyName - the new key will have this as its alias within the keystore
* @param distinguishedNameValues - contains Country, State, Locality,...,Common Name, etc.
*/
public static void createKeystoreAndKey( String storePath, char[] password,
String keyName, DistinguishedNameValues distinguishedNameValues)
{
public static void createKeystoreAndKey(String storePath, char[] password,
String keyName, DistinguishedNameValues distinguishedNameValues) {
createKeystoreAndKey(storePath, password, "RSA", 2048, keyName, password, "SHA1withRSA", 30,
distinguishedNameValues);
distinguishedNameValues);
}
public static KeySet createKeystoreAndKey( String storePath, char[] storePass,
String keyAlgorithm, int keySize, String keyName, char[] keyPass,
String certSignatureAlgorithm, int certValidityYears, DistinguishedNameValues distinguishedNameValues) {
public static KeySet createKeystoreAndKey(String storePath, char[] storePass,
String keyAlgorithm, int keySize, String keyName, char[] keyPass,
String certSignatureAlgorithm, int certValidityYears, DistinguishedNameValues distinguishedNameValues) {
try {
KeySet keySet = createKey(keyAlgorithm, keySize, keyName, certSignatureAlgorithm, certValidityYears,
distinguishedNameValues);
distinguishedNameValues);
KeyStore privateKS = KeyStoreFileManager.createKeyStore(storePath, storePass);
privateKS.setKeyEntry(keyName, keySet.getPrivateKey(),
keyPass,
new java.security.cert.Certificate[]{keySet.getPublicKey()});
keyPass,
new java.security.cert.Certificate[]{keySet.getPublicKey()});
File sfile = new File(storePath);
if (sfile.exists()) {
throw new IOException("File already exists: " + storePath);
}
KeyStoreFileManager.writeKeyStore( privateKS, storePath, storePass);
KeyStoreFileManager.writeKeyStore(privateKS, storePath, storePass);
return keySet;
} catch (RuntimeException x) {
throw x;
} catch ( Exception x) {
throw new RuntimeException( x.getMessage(), x);
}
}
/** Create a new key and store it in an existing keystore.
*
*/
public static KeySet createKey( String storePath, char[] storePass,
String keyAlgorithm, int keySize, String keyName, char[] keyPass,
String certSignatureAlgorithm, int certValidityYears,
DistinguishedNameValues distinguishedNameValues) {
try {
KeySet keySet = createKey(keyAlgorithm, keySize, keyName, certSignatureAlgorithm, certValidityYears,
distinguishedNameValues);
KeyStore privateKS = KeyStoreFileManager.loadKeyStore(storePath, storePass);
privateKS.setKeyEntry(keyName, keySet.getPrivateKey(),
keyPass,
new java.security.cert.Certificate[]{keySet.getPublicKey()});
KeyStoreFileManager.writeKeyStore( privateKS, storePath, storePass);
return keySet;
} catch (RuntimeException x) {
throw x;
} catch ( Exception x) {
} catch (Exception x) {
throw new RuntimeException(x.getMessage(), x);
}
}
public static KeySet createKey( String keyAlgorithm, int keySize, String keyName,
String certSignatureAlgorithm, int certValidityYears, DistinguishedNameValues distinguishedNameValues)
{
/**
* Create a new key and store it in an existing keystore.
*/
public static KeySet createKey(String storePath, char[] storePass,
String keyAlgorithm, int keySize, String keyName, char[] keyPass,
String certSignatureAlgorithm, int certValidityYears,
DistinguishedNameValues distinguishedNameValues) {
try {
KeySet keySet = createKey(keyAlgorithm, keySize, keyName, certSignatureAlgorithm, certValidityYears,
distinguishedNameValues);
KeyStore privateKS = KeyStoreFileManager.loadKeyStore(storePath, storePass);
privateKS.setKeyEntry(keyName, keySet.getPrivateKey(),
keyPass,
new java.security.cert.Certificate[]{keySet.getPublicKey()});
KeyStoreFileManager.writeKeyStore(privateKS, storePath, storePass);
return keySet;
} catch (RuntimeException x) {
throw x;
} catch (Exception x) {
throw new RuntimeException(x.getMessage(), x);
}
}
public static KeySet createKey(String keyAlgorithm, int keySize, String keyName,
String certSignatureAlgorithm, int certValidityYears, DistinguishedNameValues distinguishedNameValues) {
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(keyAlgorithm);
keyPairGenerator.initialize(keySize);
@ -110,15 +113,15 @@ public class CertCreator {
serialNumber = BigInteger.valueOf(new SecureRandom().nextInt());
}
v3CertGen.setSerialNumber(serialNumber);
v3CertGen.setIssuerDN( principal);
v3CertGen.setIssuerDN(principal);
v3CertGen.setNotBefore(new Date(System.currentTimeMillis() - 1000L * 60L * 60L * 24L * 30L));
v3CertGen.setNotAfter(new Date(System.currentTimeMillis() + (1000L * 60L * 60L * 24L * 366L * (long)certValidityYears)));
v3CertGen.setNotAfter(new Date(System.currentTimeMillis() + (1000L * 60L * 60L * 24L * 366L * (long) certValidityYears)));
v3CertGen.setSubjectDN(principal);
v3CertGen.setPublicKey(KPair.getPublic());
v3CertGen.setSignatureAlgorithm(certSignatureAlgorithm);
X509Certificate PKCertificate = v3CertGen.generate(KPair.getPrivate(),"BC");
X509Certificate PKCertificate = v3CertGen.generate(KPair.getPrivate(), "BC");
KeySet keySet = new KeySet();
keySet.setName(keyName);

View File

@ -1,3 +1,4 @@
package kellinwood.security.zipsigner.optional;
import kellinwood.security.zipsigner.ZipSigner;
@ -12,26 +13,27 @@ import java.security.cert.X509Certificate;
*/
public class CustomKeySigner {
/** KeyStore-type agnostic. This method will sign the zip file, automatically handling JKS or BKS keystores. */
public static void signZip( ZipSigner zipSigner,
String keystorePath,
char[] keystorePw,
String certAlias,
char[] certPw,
String signatureAlgorithm,
String inputZipFilename,
String outputZipFilename)
throws Exception
{
/**
* KeyStore-type agnostic. This method will sign the zip file, automatically handling JKS or BKS keystores.
*/
public static void signZip(ZipSigner zipSigner,
String keystorePath,
char[] keystorePw,
String certAlias,
char[] certPw,
String signatureAlgorithm,
String inputZipFilename,
String outputZipFilename)
throws Exception {
zipSigner.issueLoadingCertAndKeysProgressEvent();
KeyStore keystore = KeyStoreFileManager.loadKeyStore( keystorePath, keystorePw);
KeyStore keystore = KeyStoreFileManager.loadKeyStore(keystorePath, keystorePw);
Certificate cert = keystore.getCertificate(certAlias);
X509Certificate publicKey = (X509Certificate)cert;
X509Certificate publicKey = (X509Certificate) cert;
Key key = keystore.getKey(certAlias, certPw);
PrivateKey privateKey = (PrivateKey)key;
PrivateKey privateKey = (PrivateKey) key;
zipSigner.setKeys( "custom", publicKey, privateKey, signatureAlgorithm, null);
zipSigner.signZip( inputZipFilename, outputZipFilename);
zipSigner.setKeys("custom", publicKey, privateKey, signatureAlgorithm, null);
zipSigner.signZip(inputZipFilename, outputZipFilename);
}
}

View File

@ -1,10 +1,10 @@
package kellinwood.security.zipsigner.optional;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.jce.X509Principal;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Vector;
@ -13,61 +13,61 @@ import java.util.Vector;
/**
* Helper class for dealing with the distinguished name RDNs.
*/
public class DistinguishedNameValues extends LinkedHashMap<ASN1ObjectIdentifier,String> {
public class DistinguishedNameValues extends LinkedHashMap<ASN1ObjectIdentifier, String> {
public DistinguishedNameValues() {
put(BCStyle.C,null);
put(BCStyle.ST,null);
put(BCStyle.L,null);
put(BCStyle.STREET,null);
put(BCStyle.O,null);
put(BCStyle.OU,null);
put(BCStyle.CN,null);
put(BCStyle.C, null);
put(BCStyle.ST, null);
put(BCStyle.L, null);
put(BCStyle.STREET, null);
put(BCStyle.O, null);
put(BCStyle.OU, null);
put(BCStyle.CN, null);
}
public String put(ASN1ObjectIdentifier oid, String value) {
if (value != null && value.equals("")) value = null;
if (containsKey(oid)) super.put(oid,value); // preserve original ordering
if (containsKey(oid)) super.put(oid, value); // preserve original ordering
else {
super.put(oid,value);
super.put(oid, value);
// String cn = remove(BCStyle.CN); // CN will always be last.
// put(BCStyle.CN,cn);
}
return value;
}
public void setCountry( String country) {
put(BCStyle.C,country);
public void setCountry(String country) {
put(BCStyle.C, country);
}
public void setState( String state) {
put(BCStyle.ST,state);
public void setState(String state) {
put(BCStyle.ST, state);
}
public void setLocality( String locality) {
put(BCStyle.L,locality);
public void setLocality(String locality) {
put(BCStyle.L, locality);
}
public void setStreet( String street) {
put( BCStyle.STREET, street);
public void setStreet(String street) {
put(BCStyle.STREET, street);
}
public void setOrganization( String organization) {
put(BCStyle.O,organization);
public void setOrganization(String organization) {
put(BCStyle.O, organization);
}
public void setOrganizationalUnit( String organizationalUnit) {
put(BCStyle.OU,organizationalUnit);
public void setOrganizationalUnit(String organizationalUnit) {
put(BCStyle.OU, organizationalUnit);
}
public void setCommonName( String commonName) {
put(BCStyle.CN,commonName);
public void setCommonName(String commonName) {
put(BCStyle.CN, commonName);
}
@Override
public int size() {
int result = 0;
for( String value : values()) {
for (String value : values()) {
if (value != null) result += 1;
}
return result;
@ -77,13 +77,13 @@ public class DistinguishedNameValues extends LinkedHashMap<ASN1ObjectIdentifier,
Vector<ASN1ObjectIdentifier> oids = new Vector<ASN1ObjectIdentifier>();
Vector<String> values = new Vector<String>();
for (Map.Entry<ASN1ObjectIdentifier,String> entry : entrySet()) {
for (Map.Entry<ASN1ObjectIdentifier, String> entry : entrySet()) {
if (entry.getValue() != null && !entry.getValue().equals("")) {
oids.add( entry.getKey());
values.add( entry.getValue());
oids.add(entry.getKey());
values.add(entry.getValue());
}
}
return new X509Principal(oids,values);
return new X509Principal(oids, values);
}
}

View File

@ -1,3 +1,4 @@
package kellinwood.security.zipsigner.optional;
import kellinwood.logging.LoggerInterface;
@ -15,34 +16,34 @@ public class Fingerprint {
static LoggerInterface logger = LoggerManager.getLogger(Fingerprint.class.getName());
static byte[] calcDigest( String algorithm, byte[] encodedCert) {
static byte[] calcDigest(String algorithm, byte[] encodedCert) {
byte[] result = null;
try {
MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
messageDigest.update(encodedCert);
result = messageDigest.digest();
} catch (Exception x) {
logger.error(x.getMessage(),x);
logger.error(x.getMessage(), x);
}
return result;
}
public static String hexFingerprint( String algorithm, byte[] encodedCert) {
public static String hexFingerprint(String algorithm, byte[] encodedCert) {
try {
byte[] digest = calcDigest(algorithm,encodedCert);
byte[] digest = calcDigest(algorithm, encodedCert);
if (digest == null) return null;
HexTranslator hexTranslator = new HexTranslator();
byte[] hex = new byte[digest.length * 2];
hexTranslator.encode(digest, 0, digest.length, hex, 0);
StringBuilder builder = new StringBuilder();
for (int i = 0; i < hex.length; i += 2) {
builder.append((char)hex[i]);
builder.append((char)hex[i+1]);
builder.append((char) hex[i]);
builder.append((char) hex[i + 1]);
if (i != (hex.length - 2)) builder.append(':');
}
return builder.toString().toUpperCase();
} catch (Exception x) {
logger.error(x.getMessage(),x);
logger.error(x.getMessage(), x);
}
return null;
}
@ -55,14 +56,14 @@ public class Fingerprint {
//
// }
public static String base64Fingerprint( String algorithm, byte[] encodedCert) {
public static String base64Fingerprint(String algorithm, byte[] encodedCert) {
String result = null;
try {
byte[] digest = calcDigest(algorithm,encodedCert);
byte[] digest = calcDigest(algorithm, encodedCert);
if (digest == null) return result;
return Base64.encode(digest);
} catch (Exception x) {
logger.error(x.getMessage(),x);
logger.error(x.getMessage(), x);
}
return result;
}

View File

@ -23,15 +23,17 @@ power to enforce restrictions on reverse-engineering of their software,
and it is irresponsible for them to claim they can. */
package kellinwood.security.zipsigner.optional;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.DigestInputStream;
import java.security.DigestOutputStream;
import java.security.Key;
@ -42,29 +44,24 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.spec.SecretKeySpec;
/**
* This is an implementation of Sun's proprietary key store
* algorithm, called "JKS" for "Java Key Store". This implementation was
* created entirely through reverse-engineering.
*
* <p>
* <p>The format of JKS files is, from the start of the file:
*
* <p>
* <ol>
* <li>Magic bytes. This is a four-byte integer, in big-endian byte
* order, equal to <code>0xFEEDFEED</code>.</li>
@ -74,46 +71,46 @@ import javax.crypto.spec.SecretKeySpec;
* <li>The number of entrires in this keystore, as a four-byte
* integer. Call this value <i>n</i></li>
* <li>Then, <i>n</i> times:
* <ol>
* <li>The entry type, a four-byte int. The value 1 denotes a private
* key entry, and 2 denotes a trusted certificate.</li>
* <li>The entry's alias, formatted as strings such as those written
* by <a
* href="http://java.sun.com/j2se/1.4.1/docs/api/java/io/DataOutput.html#writeUTF(java.lang.String)">DataOutput.writeUTF(String)</a>.</li>
* <li>An eight-byte integer, representing the entry's creation date,
* in milliseconds since the epoch.
*
* <p>Then, if the entry is a private key entry:
* <ol>
* <li>The size of the encoded key as a four-byte int, then that
* number of bytes. The encoded key is the DER encoded bytes of the
* <a
* href="http://java.sun.com/j2se/1.4.1/docs/api/javax/crypto/EncryptedPrivateKeyInfo.html">EncryptedPrivateKeyInfo</a> structure (the
* encryption algorithm is discussed later).</li>
* <li>A four-byte integer, followed by that many encoded
* certificates, encoded as described in the trusted certificates
* section.</li>
* </ol>
*
* <p>Otherwise, the entry is a trusted certificate, which is encoded
* as the name of the encoding algorithm (e.g. X.509), encoded the same
* way as alias names. Then, a four-byte integer representing the size
* of the encoded certificate, then that many bytes representing the
* encoded certificate (e.g. the DER bytes in the case of X.509).
* </li>
* </ol>
* <ol>
* <li>The entry type, a four-byte int. The value 1 denotes a private
* key entry, and 2 denotes a trusted certificate.</li>
* <li>The entry's alias, formatted as strings such as those written
* by <a
* href="http://java.sun.com/j2se/1.4.1/docs/api/java/io/DataOutput.html#writeUTF(java.lang.String)">DataOutput.writeUTF(String)</a>.</li>
* <li>An eight-byte integer, representing the entry's creation date,
* in milliseconds since the epoch.
* <p>
* <p>Then, if the entry is a private key entry:
* <ol>
* <li>The size of the encoded key as a four-byte int, then that
* number of bytes. The encoded key is the DER encoded bytes of the
* <a
* href="http://java.sun.com/j2se/1.4.1/docs/api/javax/crypto/EncryptedPrivateKeyInfo.html">EncryptedPrivateKeyInfo</a> structure (the
* encryption algorithm is discussed later).</li>
* <li>A four-byte integer, followed by that many encoded
* certificates, encoded as described in the trusted certificates
* section.</li>
* </ol>
* <p>
* <p>Otherwise, the entry is a trusted certificate, which is encoded
* as the name of the encoding algorithm (e.g. X.509), encoded the same
* way as alias names. Then, a four-byte integer representing the size
* of the encoded certificate, then that many bytes representing the
* encoded certificate (e.g. the DER bytes in the case of X.509).
* </li>
* </ol>
* </li>
* <li>Then, the signature.</li>
* </ol>
* </ol>
* </li>
* </ol>
*
* <p>
* <p>(See <a href="genkey.java">this file</a> for some idea of how I
* was able to figure out these algorithms)</p>
*
* <p>
* <p>Decrypting the key works as follows:
*
* <p>
* <ol>
* <li>The key length is the length of the ciphertext minus 40. The
* encrypted key, <code>ekey</code>, is the middle bytes of the
@ -129,36 +126,37 @@ import javax.crypto.spec.SecretKeySpec;
* the last 20 bytes of the ciphertext, output <code>FAIL</code>. Otherwise,
* output <code>key</code>.</li>
* </ol>
*
* <p>
* <p>The signature is defined as <code>SHA-1(UTF-16BE(password) +
* US_ASCII("Mighty Aphrodite") + encoded_keystore)</code> (yup, Sun
* engineers are just that clever).
*
* <p>
* <p>(Above, SHA-1 denotes the secure hash algorithm, UTF-16BE the
* big-endian byte representation of a UTF-16 string, and US_ASCII the
* ASCII byte representation of the string.)
*
* <p>
* <p>The source code of this class should be available in the file <a
* href="http://metastatic.org/source/JKS.java">JKS.java</a>.
*
* @author Casey Marshall (rsdio@metastatic.org)
*
* Changes by Ken Ellinwood:
* ** Fixed a NullPointerException in engineLoad(). This method must return gracefully if the keystore input stream is null.
* ** engineGetCertificateEntry() was updated to return the first cert in the chain for private key entries.
* ** Lowercase the alias names, otherwise keytool chokes on the file created by this code.
* ** Fixed the integrity check in engineLoad(), previously the exception was never thrown regardless of password value.
* <p>
* Changes by Ken Ellinwood:
* ** Fixed a NullPointerException in engineLoad(). This method must return gracefully if the keystore input stream is null.
* ** engineGetCertificateEntry() was updated to return the first cert in the chain for private key entries.
* ** Lowercase the alias names, otherwise keytool chokes on the file created by this code.
* ** Fixed the integrity check in engineLoad(), previously the exception was never thrown regardless of password value.
*/
public class JKS extends KeyStoreSpi
{
public class JKS extends KeyStoreSpi {
// Constants and fields.
// ------------------------------------------------------------------------
/** Ah, Sun. So goddamned clever with those magic bytes. */
/**
* Ah, Sun. So goddamned clever with those magic bytes.
*/
private static final int MAGIC = 0xFEEDFEED;
private static final int PRIVATE_KEY = 1;
private static final int PRIVATE_KEY = 1;
private static final int TRUSTED_CERT = 2;
private final Vector aliases;
@ -170,8 +168,7 @@ public class JKS extends KeyStoreSpi
// Constructor.
// ------------------------------------------------------------------------
public JKS()
{
public JKS() {
super();
aliases = new Vector();
trustedCerts = new HashMap();
@ -185,41 +182,33 @@ public class JKS extends KeyStoreSpi
// ------------------------------------------------------------------------
public Key engineGetKey(String alias, char[] password)
throws NoSuchAlgorithmException, UnrecoverableKeyException
{
throws NoSuchAlgorithmException, UnrecoverableKeyException {
alias = alias.toLowerCase();
if (!privateKeys.containsKey(alias))
return null;
byte[] key = decryptKey((byte[]) privateKeys.get(alias),
charsToBytes(password));
charsToBytes(password));
Certificate[] chain = engineGetCertificateChain(alias);
if (chain.length > 0)
{
try
{
if (chain.length > 0) {
try {
// Private and public keys MUST have the same algorithm.
KeyFactory fact = KeyFactory.getInstance(
chain[0].getPublicKey().getAlgorithm());
chain[0].getPublicKey().getAlgorithm());
return fact.generatePrivate(new PKCS8EncodedKeySpec(key));
}
catch (InvalidKeySpecException x)
{
} catch (InvalidKeySpecException x) {
throw new UnrecoverableKeyException(x.getMessage());
}
}
else
} else
return new SecretKeySpec(key, alias);
}
public Certificate[] engineGetCertificateChain(String alias)
{
public Certificate[] engineGetCertificateChain(String alias) {
alias = alias.toLowerCase();
return (Certificate[]) certChains.get(alias);
}
public Certificate engineGetCertificate(String alias)
{
public Certificate engineGetCertificate(String alias) {
alias = alias.toLowerCase();
if (engineIsKeyEntry(alias)) {
Certificate[] certChain = (Certificate[]) certChains.get(alias);
@ -228,8 +217,7 @@ public class JKS extends KeyStoreSpi
return (Certificate) trustedCerts.get(alias);
}
public Date engineGetCreationDate(String alias)
{
public Date engineGetCreationDate(String alias) {
alias = alias.toLowerCase();
return (Date) dates.get(alias);
}
@ -237,8 +225,7 @@ public class JKS extends KeyStoreSpi
// XXX implement writing methods.
public void engineSetKeyEntry(String alias, Key key, char[] passwd, Certificate[] certChain)
throws KeyStoreException
{
throws KeyStoreException {
alias = alias.toLowerCase();
if (trustedCerts.containsKey(alias))
throw new KeyStoreException("\"" + alias + " is a trusted certificate entry");
@ -247,25 +234,20 @@ public class JKS extends KeyStoreSpi
certChains.put(alias, certChain);
else
certChains.put(alias, new Certificate[0]);
if (!aliases.contains(alias))
{
if (!aliases.contains(alias)) {
dates.put(alias, new Date());
aliases.add(alias);
}
}
public void engineSetKeyEntry(String alias, byte[] encodedKey, Certificate[] certChain)
throws KeyStoreException
{
throws KeyStoreException {
alias = alias.toLowerCase();
if (trustedCerts.containsKey(alias))
throw new KeyStoreException("\"" + alias + "\" is a trusted certificate entry");
try
{
try {
new EncryptedPrivateKeyInfo(encodedKey);
}
catch (IOException ioe)
{
} catch (IOException ioe) {
throw new KeyStoreException("encoded key is not an EncryptedPrivateKeyInfo");
}
privateKeys.put(alias, encodedKey);
@ -273,67 +255,56 @@ public class JKS extends KeyStoreSpi
certChains.put(alias, certChain);
else
certChains.put(alias, new Certificate[0]);
if (!aliases.contains(alias))
{
if (!aliases.contains(alias)) {
dates.put(alias, new Date());
aliases.add(alias);
}
}
public void engineSetCertificateEntry(String alias, Certificate cert)
throws KeyStoreException
{
throws KeyStoreException {
alias = alias.toLowerCase();
if (privateKeys.containsKey(alias))
throw new KeyStoreException("\"" + alias + "\" is a private key entry");
if (cert == null)
throw new NullPointerException();
trustedCerts.put(alias, cert);
if (!aliases.contains(alias))
{
if (!aliases.contains(alias)) {
dates.put(alias, new Date());
aliases.add(alias);
}
}
public void engineDeleteEntry(String alias) throws KeyStoreException
{
public void engineDeleteEntry(String alias) throws KeyStoreException {
alias = alias.toLowerCase();
aliases.remove(alias);
}
public Enumeration engineAliases()
{
public Enumeration engineAliases() {
return aliases.elements();
}
public boolean engineContainsAlias(String alias)
{
public boolean engineContainsAlias(String alias) {
alias = alias.toLowerCase();
return aliases.contains(alias);
}
public int engineSize()
{
public int engineSize() {
return aliases.size();
}
public boolean engineIsKeyEntry(String alias)
{
public boolean engineIsKeyEntry(String alias) {
alias = alias.toLowerCase();
return privateKeys.containsKey(alias);
}
public boolean engineIsCertificateEntry(String alias)
{
public boolean engineIsCertificateEntry(String alias) {
alias = alias.toLowerCase();
return trustedCerts.containsKey(alias);
}
public String engineGetCertificateAlias(Certificate cert)
{
for (Iterator keys = trustedCerts.keySet().iterator(); keys.hasNext(); )
{
public String engineGetCertificateAlias(Certificate cert) {
for (Iterator keys = trustedCerts.keySet().iterator(); keys.hasNext(); ) {
String alias = (String) keys.next();
if (cert.equals(trustedCerts.get(alias)))
return alias;
@ -342,8 +313,7 @@ public class JKS extends KeyStoreSpi
}
public void engineStore(OutputStream out, char[] passwd)
throws IOException, NoSuchAlgorithmException, CertificateException
{
throws IOException, NoSuchAlgorithmException, CertificateException {
MessageDigest md = MessageDigest.getInstance("SHA1");
md.update(charsToBytes(passwd));
md.update("Mighty Aphrodite".getBytes("UTF-8"));
@ -351,18 +321,14 @@ public class JKS extends KeyStoreSpi
dout.writeInt(MAGIC);
dout.writeInt(2);
dout.writeInt(aliases.size());
for (Enumeration e = aliases.elements(); e.hasMoreElements(); )
{
for (Enumeration e = aliases.elements(); e.hasMoreElements(); ) {
String alias = (String) e.nextElement();
if (trustedCerts.containsKey(alias))
{
if (trustedCerts.containsKey(alias)) {
dout.writeInt(TRUSTED_CERT);
dout.writeUTF(alias);
dout.writeLong(((Date) dates.get(alias)).getTime());
writeCert(dout, (Certificate) trustedCerts.get(alias));
}
else
{
} else {
dout.writeInt(PRIVATE_KEY);
dout.writeUTF(alias);
dout.writeLong(((Date) dates.get(alias)).getTime());
@ -380,8 +346,7 @@ public class JKS extends KeyStoreSpi
}
public void engineLoad(InputStream in, char[] passwd)
throws IOException, NoSuchAlgorithmException, CertificateException
{
throws IOException, NoSuchAlgorithmException, CertificateException {
MessageDigest md = MessageDigest.getInstance("SHA");
if (passwd != null) md.update(charsToBytes(passwd));
md.update("Mighty Aphrodite".getBytes("UTF-8")); // HAR HAR
@ -399,14 +364,12 @@ public class JKS extends KeyStoreSpi
aliases.ensureCapacity(n);
if (n < 0)
throw new LoadKeystoreException("Malformed key store");
for (int i = 0; i < n; i++)
{
for (int i = 0; i < n; i++) {
int type = din.readInt();
String alias = din.readUTF();
aliases.add(alias);
dates.put(alias, new Date(din.readLong()));
switch (type)
{
switch (type) {
case PRIVATE_KEY:
int len = din.readInt();
byte[] encoded = new byte[len];
@ -442,8 +405,7 @@ public class JKS extends KeyStoreSpi
// ------------------------------------------------------------------------
private static Certificate readCert(DataInputStream in)
throws IOException, CertificateException, NoSuchAlgorithmException
{
throws IOException, CertificateException, NoSuchAlgorithmException {
String type = in.readUTF();
int len = in.readInt();
byte[] encoded = new byte[len];
@ -453,8 +415,7 @@ public class JKS extends KeyStoreSpi
}
private static void writeCert(DataOutputStream dout, Certificate cert)
throws IOException, CertificateException
{
throws IOException, CertificateException {
dout.writeUTF(cert.getType());
byte[] b = cert.getEncoded();
dout.writeInt(b.length);
@ -462,29 +423,25 @@ public class JKS extends KeyStoreSpi
}
private static byte[] decryptKey(byte[] encryptedPKI, byte[] passwd)
throws UnrecoverableKeyException
{
try
{
throws UnrecoverableKeyException {
try {
EncryptedPrivateKeyInfo epki =
new EncryptedPrivateKeyInfo(encryptedPKI);
new EncryptedPrivateKeyInfo(encryptedPKI);
byte[] encr = epki.getEncryptedData();
byte[] keystream = new byte[20];
System.arraycopy(encr, 0, keystream, 0, 20);
byte[] check = new byte[20];
System.arraycopy(encr, encr.length-20, check, 0, 20);
System.arraycopy(encr, encr.length - 20, check, 0, 20);
byte[] key = new byte[encr.length - 40];
MessageDigest sha = MessageDigest.getInstance("SHA1");
int count = 0;
while (count < key.length)
{
while (count < key.length) {
sha.reset();
sha.update(passwd);
sha.update(keystream);
sha.digest(keystream, 0, keystream.length);
for (int i = 0; i < keystream.length && count < key.length; i++)
{
key[count] = (byte) (keystream[i] ^ encr[count+20]);
for (int i = 0; i < keystream.length && count < key.length; i++) {
key[count] = (byte) (keystream[i] ^ encr[count + 20]);
count++;
}
}
@ -494,18 +451,14 @@ public class JKS extends KeyStoreSpi
if (!MessageDigest.isEqual(check, sha.digest()))
throw new UnrecoverableKeyException("checksum mismatch");
return key;
}
catch (Exception x)
{
} catch (Exception x) {
throw new UnrecoverableKeyException(x.getMessage());
}
}
private static byte[] encryptKey(Key key, byte[] passwd)
throws KeyStoreException
{
try
{
throws KeyStoreException {
try {
MessageDigest sha = MessageDigest.getInstance("SHA1");
SecureRandom rand = SecureRandom.getInstance("SHA1PRNG");
byte[] k = key.getEncoded();
@ -513,15 +466,13 @@ public class JKS extends KeyStoreSpi
byte[] keystream = rand.getSeed(20);
System.arraycopy(keystream, 0, encrypted, 0, 20);
int count = 0;
while (count < k.length)
{
while (count < k.length) {
sha.reset();
sha.update(passwd);
sha.update(keystream);
sha.digest(keystream, 0, keystream.length);
for (int i = 0; i < keystream.length && count < k.length; i++)
{
encrypted[count+20] = (byte) (keystream[i] ^ k[count]);
for (int i = 0; i < keystream.length && count < k.length; i++) {
encrypted[count + 20] = (byte) (keystream[i] ^ k[count]);
count++;
}
}
@ -532,21 +483,17 @@ public class JKS extends KeyStoreSpi
// 1.3.6.1.4.1.42.2.17.1.1 is Sun's private OID for this
// encryption algorithm.
return new EncryptedPrivateKeyInfo("1.3.6.1.4.1.42.2.17.1.1",
encrypted).getEncoded();
}
catch (Exception x)
{
encrypted).getEncoded();
} catch (Exception x) {
throw new KeyStoreException(x.getMessage());
}
}
private static byte[] charsToBytes(char[] passwd)
{
private static byte[] charsToBytes(char[] passwd) {
byte[] buf = new byte[passwd.length * 2];
for (int i = 0, j = 0; i < passwd.length; i++)
{
for (int i = 0, j = 0; i < passwd.length; i++) {
buf[j++] = (byte) (passwd[i] >>> 8);
buf[j++] = (byte) passwd[i];
buf[j++] = (byte) passwd[i];
}
return buf;
}

View File

@ -1,3 +1,4 @@
package kellinwood.security.zipsigner.optional;

View File

@ -1,3 +1,4 @@
package kellinwood.security.zipsigner.optional;
public class KeyNameConflictException extends Exception {

View File

@ -1,3 +1,4 @@
package kellinwood.security.zipsigner.optional;
@ -5,27 +6,36 @@ import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.*;
import java.security.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.Key;
import java.security.KeyStore;
import java.security.Provider;
import java.security.Security;
import java.security.cert.Certificate;
/**
*/
public class KeyStoreFileManager {
static Provider provider = new BouncyCastleProvider();
public static Provider getProvider() { return provider; }
public static void setProvider(Provider provider) {
if (KeyStoreFileManager.provider != null) Security.removeProvider( KeyStoreFileManager.provider.getName());
KeyStoreFileManager.provider = provider;
Security.addProvider( provider);
public static Provider getProvider() {
return provider;
}
static LoggerInterface logger = LoggerManager.getLogger( KeyStoreFileManager.class.getName());
public static void setProvider(Provider provider) {
if (KeyStoreFileManager.provider != null) Security.removeProvider(KeyStoreFileManager.provider.getName());
KeyStoreFileManager.provider = provider;
Security.addProvider(provider);
}
static LoggerInterface logger = LoggerManager.getLogger(KeyStoreFileManager.class.getName());
static {
// Add the bouncycastle version of the BC provider so that the implementation classes returned
@ -34,12 +44,12 @@ public class KeyStoreFileManager {
}
public static KeyStore loadKeyStore( String keystorePath, String encodedPassword)
throws Exception{
public static KeyStore loadKeyStore(String keystorePath, String encodedPassword)
throws Exception {
char password[] = null;
try {
if (encodedPassword != null) {
password = PasswordObfuscator.getInstance().decodeKeystorePassword( keystorePath, encodedPassword);
password = PasswordObfuscator.getInstance().decodeKeystorePassword(keystorePath, encodedPassword);
}
return loadKeyStore(keystorePath, password);
} finally {
@ -47,27 +57,24 @@ public class KeyStoreFileManager {
}
}
public static KeyStore createKeyStore( String keystorePath, char[] password)
throws Exception
{
public static KeyStore createKeyStore(String keystorePath, char[] password)
throws Exception {
KeyStore ks = null;
if (keystorePath.toLowerCase().endsWith(".bks")) {
ks = KeyStore.getInstance("bks", new BouncyCastleProvider());
}
else ks = new JksKeyStore();
} else ks = new JksKeyStore();
ks.load(null, password);
return ks;
}
public static KeyStore loadKeyStore( String keystorePath, char[] password)
throws Exception
{
public static KeyStore loadKeyStore(String keystorePath, char[] password)
throws Exception {
KeyStore ks = null;
try {
ks = new JksKeyStore();
FileInputStream fis = new FileInputStream( keystorePath);
ks.load( fis, password);
FileInputStream fis = new FileInputStream(keystorePath);
ks.load(fis, password);
fis.close();
return ks;
} catch (LoadKeystoreException x) {
@ -78,8 +85,8 @@ public class KeyStoreFileManager {
// logger.warning( x.getMessage(), x);
try {
ks = KeyStore.getInstance("bks", getProvider());
FileInputStream fis = new FileInputStream( keystorePath);
ks.load( fis, password);
FileInputStream fis = new FileInputStream(keystorePath);
ks.load(fis, password);
fis.close();
return ks;
} catch (Exception e) {
@ -88,29 +95,27 @@ public class KeyStoreFileManager {
}
}
public static void writeKeyStore( KeyStore ks, String keystorePath, String encodedPassword)
throws Exception
{
public static void writeKeyStore(KeyStore ks, String keystorePath, String encodedPassword)
throws Exception {
char password[] = null;
try {
password = PasswordObfuscator.getInstance().decodeKeystorePassword( keystorePath, encodedPassword);
writeKeyStore( ks, keystorePath, password);
password = PasswordObfuscator.getInstance().decodeKeystorePassword(keystorePath, encodedPassword);
writeKeyStore(ks, keystorePath, password);
} finally {
if (password != null) PasswordObfuscator.flush(password);
}
}
public static void writeKeyStore( KeyStore ks, String keystorePath, char[] password)
throws Exception
{
public static void writeKeyStore(KeyStore ks, String keystorePath, char[] password)
throws Exception {
File keystoreFile = new File( keystorePath);
File keystoreFile = new File(keystorePath);
try {
if (keystoreFile.exists()) {
// I've had some trouble saving new verisons of the keystore file in which the file becomes empty/corrupt.
// Saving the new version to a new file and creating a backup of the old version.
File tmpFile = File.createTempFile( keystoreFile.getName(), null, keystoreFile.getParentFile());
FileOutputStream fos = new FileOutputStream( tmpFile);
File tmpFile = File.createTempFile(keystoreFile.getName(), null, keystoreFile.getParentFile());
FileOutputStream fos = new FileOutputStream(tmpFile);
ks.store(fos, password);
fos.flush();
fos.close();
@ -125,18 +130,19 @@ public class KeyStoreFileManager {
*/
renameTo(tmpFile, keystoreFile);
} else {
FileOutputStream fos = new FileOutputStream( keystorePath);
FileOutputStream fos = new FileOutputStream(keystorePath);
ks.store(fos, password);
fos.close();
}
} catch (Exception x) {
try {
File logfile = File.createTempFile("zipsigner-error", ".log", keystoreFile.getParentFile());
PrintWriter pw = new PrintWriter(new FileWriter( logfile));
x.printStackTrace( pw);
PrintWriter pw = new PrintWriter(new FileWriter(logfile));
x.printStackTrace(pw);
pw.flush();
pw.close();
} catch (Exception y) {}
} catch (Exception y) {
}
throw x;
}
}
@ -159,15 +165,21 @@ public class KeyStoreFileManager {
count += n;
}
} finally {
try { output.close(); } catch (IOException x) {} // Ignore
try {
output.close();
} catch (IOException x) {
} // Ignore
}
} finally {
try { input.close(); } catch (IOException x) {}
try {
input.close();
} catch (IOException x) {
}
}
if (srcFile.length() != destFile.length()) {
throw new IOException("Failed to copy full contents from '" +
srcFile + "' to '" + destFile + "'");
srcFile + "' to '" + destFile + "'");
}
if (preserveFileDate) {
destFile.setLastModified(srcFile.lastModified());
@ -176,23 +188,20 @@ public class KeyStoreFileManager {
public static void renameTo(File fromFile, File toFile)
throws IOException
{
throws IOException {
copyFile(fromFile, toFile, true);
if (!fromFile.delete()) throw new IOException("Failed to delete " + fromFile);
}
public static void deleteKey(String storePath, String storePass, String keyName)
throws Exception
{
KeyStore ks = loadKeyStore( storePath, storePass);
ks.deleteEntry( keyName);
throws Exception {
KeyStore ks = loadKeyStore(storePath, storePass);
ks.deleteEntry(keyName);
writeKeyStore(ks, storePath, storePass);
}
public static String renameKey( String keystorePath, String storePass, String oldKeyName, String newKeyName, String keyPass)
throws Exception
{
public static String renameKey(String keystorePath, String storePass, String oldKeyName, String newKeyName, String keyPass)
throws Exception {
char[] keyPw = null;
try {
@ -201,59 +210,53 @@ public class KeyStoreFileManager {
if (ks.containsAlias(newKeyName)) throw new KeyNameConflictException();
keyPw = PasswordObfuscator.getInstance().decodeAliasPassword( keystorePath, oldKeyName, keyPass);
keyPw = PasswordObfuscator.getInstance().decodeAliasPassword(keystorePath, oldKeyName, keyPass);
Key key = ks.getKey(oldKeyName, keyPw);
Certificate cert = ks.getCertificate( oldKeyName);
Certificate cert = ks.getCertificate(oldKeyName);
ks.setKeyEntry(newKeyName, key, keyPw, new Certificate[] { cert});
ks.deleteEntry( oldKeyName);
ks.setKeyEntry(newKeyName, key, keyPw, new Certificate[]{cert});
ks.deleteEntry(oldKeyName);
writeKeyStore(ks, keystorePath, storePass);
return newKeyName;
}
finally {
} finally {
PasswordObfuscator.flush(keyPw);
}
}
public static KeyStore.Entry getKeyEntry( String keystorePath, String storePass, String keyName, String keyPass)
throws Exception
{
public static KeyStore.Entry getKeyEntry(String keystorePath, String storePass, String keyName, String keyPass)
throws Exception {
char[] keyPw = null;
KeyStore.PasswordProtection passwordProtection = null;
try {
KeyStore ks = loadKeyStore(keystorePath, storePass);
keyPw = PasswordObfuscator.getInstance().decodeAliasPassword( keystorePath, keyName, keyPass);
keyPw = PasswordObfuscator.getInstance().decodeAliasPassword(keystorePath, keyName, keyPass);
passwordProtection = new KeyStore.PasswordProtection(keyPw);
return ks.getEntry( keyName, passwordProtection);
}
finally {
return ks.getEntry(keyName, passwordProtection);
} finally {
if (keyPw != null) PasswordObfuscator.flush(keyPw);
if (passwordProtection != null) passwordProtection.destroy();
}
}
public static boolean containsKey( String keystorePath, String storePass, String keyName)
throws Exception
{
public static boolean containsKey(String keystorePath, String storePass, String keyName)
throws Exception {
KeyStore ks = loadKeyStore(keystorePath, storePass);
return ks.containsAlias( keyName);
return ks.containsAlias(keyName);
}
/**
*
* @param keystorePath
* @param encodedPassword
* @throws Exception if the password is invalid
*/
public static void validateKeystorePassword( String keystorePath, String encodedPassword)
throws Exception
{
public static void validateKeystorePassword(String keystorePath, String encodedPassword)
throws Exception {
char[] password = null;
try {
KeyStore ks = KeyStoreFileManager.loadKeyStore( keystorePath, encodedPassword);
KeyStore ks = KeyStoreFileManager.loadKeyStore(keystorePath, encodedPassword);
} finally {
if (password != null) PasswordObfuscator.flush(password);
}
@ -261,19 +264,17 @@ public class KeyStoreFileManager {
}
/**
*
* @param keystorePath
* @param keyName
* @param encodedPassword
* @throws java.security.UnrecoverableKeyException if the password is invalid
*/
public static void validateKeyPassword( String keystorePath, String keyName, String encodedPassword)
throws Exception
{
public static void validateKeyPassword(String keystorePath, String keyName, String encodedPassword)
throws Exception {
char[] password = null;
try {
KeyStore ks = KeyStoreFileManager.loadKeyStore( keystorePath, (char[])null);
password = PasswordObfuscator.getInstance().decodeAliasPassword(keystorePath,keyName, encodedPassword);
KeyStore ks = KeyStoreFileManager.loadKeyStore(keystorePath, (char[]) null);
password = PasswordObfuscator.getInstance().decodeAliasPassword(keystorePath, keyName, encodedPassword);
ks.getKey(keyName, password);
} finally {
if (password != null) PasswordObfuscator.flush(password);

View File

@ -1,3 +1,4 @@
package kellinwood.security.zipsigner.optional;
import java.io.IOException;

View File

@ -1,3 +1,4 @@
package kellinwood.security.zipsigner.optional;
import kellinwood.logging.LoggerInterface;
@ -6,7 +7,12 @@ import kellinwood.security.zipsigner.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
public class PasswordObfuscator {
@ -18,7 +24,7 @@ public class PasswordObfuscator {
SecretKeySpec skeySpec;
private PasswordObfuscator() {
logger = LoggerManager.getLogger( PasswordObfuscator.class.getName());
logger = LoggerManager.getLogger(PasswordObfuscator.class.getName());
skeySpec = new SecretKeySpec(x.getBytes(), "AES");
}
@ -27,39 +33,39 @@ public class PasswordObfuscator {
return instance;
}
public String encodeKeystorePassword( String keystorePath, String password) {
return encode( keystorePath, password);
public String encodeKeystorePassword(String keystorePath, String password) {
return encode(keystorePath, password);
}
public String encodeKeystorePassword( String keystorePath, char[] password) {
return encode( keystorePath, password);
public String encodeKeystorePassword(String keystorePath, char[] password) {
return encode(keystorePath, password);
}
public String encodeAliasPassword( String keystorePath, String aliasName, String password) {
return encode( keystorePath+aliasName, password);
public String encodeAliasPassword(String keystorePath, String aliasName, String password) {
return encode(keystorePath + aliasName, password);
}
public String encodeAliasPassword( String keystorePath, String aliasName, char[] password) {
return encode( keystorePath+aliasName, password);
public String encodeAliasPassword(String keystorePath, String aliasName, char[] password) {
return encode(keystorePath + aliasName, password);
}
public char[] decodeKeystorePassword( String keystorePath, String password) {
return decode(keystorePath,password);
public char[] decodeKeystorePassword(String keystorePath, String password) {
return decode(keystorePath, password);
}
public char[] decodeAliasPassword( String keystorePath, String aliasName, String password) {
return decode(keystorePath+aliasName,password);
public char[] decodeAliasPassword(String keystorePath, String aliasName, String password) {
return decode(keystorePath + aliasName, password);
}
public String encode( String junk, String password) {
public String encode(String junk, String password) {
if (password == null) return null;
char[] c = password.toCharArray();
String result = encode( junk, c);
String result = encode(junk, c);
flush(c);
return result;
}
public String encode( String junk, char[] password) {
public String encode(String junk, char[] password) {
if (password == null) return null;
try {
// Instantiate the cipher
@ -70,33 +76,33 @@ public class PasswordObfuscator {
w.write(junk);
w.write(password);
w.flush();
byte[] encoded = cipher.doFinal( baos.toByteArray());
return Base64.encode( encoded);
byte[] encoded = cipher.doFinal(baos.toByteArray());
return Base64.encode(encoded);
} catch (Exception x) {
logger.error("Failed to obfuscate password", x);
}
return null;
}
public char[] decode( String junk, String password) {
public char[] decode(String junk, String password) {
if (password == null) return null;
try {
// Instantiate the cipher
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec skeySpec = new SecretKeySpec(x.getBytes(), "AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] bytes = cipher.doFinal( Base64.decode(password.getBytes()));
BufferedReader r = new BufferedReader( new InputStreamReader( new ByteArrayInputStream( bytes)));
byte[] bytes = cipher.doFinal(Base64.decode(password.getBytes()));
BufferedReader r = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes)));
char[] cb = new char[128];
int length = 0;
int numRead;
while ((numRead = r.read(cb, length, 128-length)) != -1) {
while ((numRead = r.read(cb, length, 128 - length)) != -1) {
length += numRead;
}
if (length <= junk.length()) return null;
char[] result = new char[ length - junk.length()];
char[] result = new char[length - junk.length()];
int j = 0;
for (int i = junk.length(); i < length; i++) {
result[j] = cb[i];
@ -111,14 +117,14 @@ public class PasswordObfuscator {
return null;
}
public static void flush( char[] charArray) {
public static void flush(char[] charArray) {
if (charArray == null) return;
for (int i = 0; i < charArray.length; i++) {
charArray[i] = '\0';
}
}
public static void flush( byte[] charArray) {
public static void flush(byte[] charArray) {
if (charArray == null) return;
for (int i = 0; i < charArray.length; i++) {
charArray[i] = 0;

View File

@ -1,8 +1,13 @@
package kellinwood.security.zipsigner.optional;
import kellinwood.security.zipsigner.KeySet;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.*;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cms.SignerInfoGenerator;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DigestCalculatorProvider;
@ -39,11 +44,11 @@ public class SignatureBlockGenerator {
JcaDigestCalculatorProviderBuilder jcaDigestCalculatorProviderBuilder = new JcaDigestCalculatorProviderBuilder().setProvider("SC");
DigestCalculatorProvider digestCalculatorProvider = jcaDigestCalculatorProviderBuilder.build();
JcaSignerInfoGeneratorBuilder jcaSignerInfoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder( digestCalculatorProvider);
JcaSignerInfoGeneratorBuilder jcaSignerInfoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder(digestCalculatorProvider);
jcaSignerInfoGeneratorBuilder.setDirectSignature(true);
SignerInfoGenerator signerInfoGenerator = jcaSignerInfoGeneratorBuilder.build(sha1Signer, keySet.getPublicKey());
gen.addSignerInfoGenerator( signerInfoGenerator);
gen.addSignerInfoGenerator(signerInfoGenerator);
gen.addCertificates(certs);

View File

@ -13,15 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.zipio;
import java.io.IOException;
package kellinwood.zipio;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
public class CentralEnd
{
import java.io.IOException;
public class CentralEnd {
public int signature = 0x06054b50; // end of central dir signature 4 bytes
public short numberThisDisk = 0; // number of this disk 2 bytes
public short centralStartDisk = 0; // number of the disk with the start of the central directory 2 bytes
@ -34,73 +34,70 @@ public class CentralEnd
private static LoggerInterface log;
public static CentralEnd read(ZipInput input) throws IOException
{
public static CentralEnd read(ZipInput input) throws IOException {
int signature = input.readInt();
if (signature != 0x06054b50) {
// back up to the signature
input.seek( input.getFilePointer() - 4);
input.seek(input.getFilePointer() - 4);
return null;
}
CentralEnd entry = new CentralEnd();
entry.doRead( input);
entry.doRead(input);
return entry;
}
public static LoggerInterface getLogger() {
if (log == null) log = LoggerManager.getLogger( CentralEnd.class.getName());
if (log == null) log = LoggerManager.getLogger(CentralEnd.class.getName());
return log;
}
private void doRead( ZipInput input) throws IOException
{
private void doRead(ZipInput input) throws IOException {
boolean debug = getLogger().isDebugEnabled();
numberThisDisk = input.readShort();
if (debug) log.debug( String.format("This disk number: 0x%04x", numberThisDisk));
if (debug) log.debug(String.format("This disk number: 0x%04x", numberThisDisk));
centralStartDisk = input.readShort();
if (debug) log.debug( String.format("Central dir start disk number: 0x%04x", centralStartDisk));
if (debug) log.debug(String.format("Central dir start disk number: 0x%04x", centralStartDisk));
numCentralEntries = input.readShort();
if (debug) log.debug( String.format("Central entries on this disk: 0x%04x", numCentralEntries));
if (debug) log.debug(String.format("Central entries on this disk: 0x%04x", numCentralEntries));
totalCentralEntries = input.readShort();
if (debug) log.debug( String.format("Total number of central entries: 0x%04x", totalCentralEntries));
if (debug) log.debug(String.format("Total number of central entries: 0x%04x", totalCentralEntries));
centralDirectorySize = input.readInt();
if (debug) log.debug( String.format("Central directory size: 0x%08x", centralDirectorySize));
if (debug) log.debug(String.format("Central directory size: 0x%08x", centralDirectorySize));
centralStartOffset = input.readInt();
if (debug) log.debug( String.format("Central directory offset: 0x%08x", centralStartOffset));
if (debug) log.debug(String.format("Central directory offset: 0x%08x", centralStartOffset));
short zipFileCommentLen = input.readShort();
fileComment = input.readString(zipFileCommentLen);
if (debug) log.debug( ".ZIP file comment: " + fileComment);
if (debug) log.debug(".ZIP file comment: " + fileComment);
}
public void write( ZipOutput output) throws IOException
{
public void write(ZipOutput output) throws IOException {
boolean debug = getLogger().isDebugEnabled();
output.writeInt( signature);
output.writeShort( numberThisDisk);
output.writeShort( centralStartDisk);
output.writeShort( numCentralEntries);
output.writeShort( totalCentralEntries);
output.writeInt( centralDirectorySize );
output.writeInt( centralStartOffset );
output.writeShort( (short)fileComment.length());
output.writeString( fileComment);
output.writeInt(signature);
output.writeShort(numberThisDisk);
output.writeShort(centralStartDisk);
output.writeShort(numCentralEntries);
output.writeShort(totalCentralEntries);
output.writeInt(centralDirectorySize);
output.writeInt(centralStartOffset);
output.writeShort((short) fileComment.length());
output.writeString(fileComment);
}

View File

@ -13,13 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.zipio;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.File;
import java.io.OutputStream;
import java.io.SequenceInputStream;
import java.util.Date;
@ -27,9 +31,6 @@ import java.util.zip.CRC32;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
public class ZioEntry implements Cloneable {
private ZipInput zipInput;
@ -62,36 +63,35 @@ public class ZioEntry implements Cloneable {
private static LoggerInterface log;
public ZioEntry( ZipInput input) {
public ZioEntry(ZipInput input) {
zipInput = input;
}
public static LoggerInterface getLogger() {
if (log == null) log = LoggerManager.getLogger( ZioEntry.class.getName());
if (log == null) log = LoggerManager.getLogger(ZioEntry.class.getName());
return log;
}
public ZioEntry( String name) {
public ZioEntry(String name) {
filename = name;
fileComment = "";
compression = 8;
extraData = new byte[0];
setTime( System.currentTimeMillis());
setTime(System.currentTimeMillis());
}
public ZioEntry( String name, String sourceDataFile)
throws IOException
{
zipInput = new ZipInput( sourceDataFile);
public ZioEntry(String name, String sourceDataFile)
throws IOException {
zipInput = new ZipInput(sourceDataFile);
filename = name;
fileComment = "";
this.compression = 0;
this.size = (int)zipInput.getFileLength();
this.size = (int) zipInput.getFileLength();
this.compressedSize = this.size;
if (getLogger().isDebugEnabled())
getLogger().debug(String.format("Computing CRC for %s, size=%d",sourceDataFile,size));
getLogger().debug(String.format("Computing CRC for %s, size=%d", sourceDataFile, size));
// compute CRC
CRC32 crc = new CRC32();
@ -100,27 +100,25 @@ public class ZioEntry implements Cloneable {
int numRead = 0;
while (numRead != size) {
int count = zipInput.read( buffer, 0, Math.min( buffer.length, (this.size - numRead)));
int count = zipInput.read(buffer, 0, Math.min(buffer.length, (this.size - numRead)));
if (count > 0) {
crc.update( buffer, 0, count);
crc.update(buffer, 0, count);
numRead += count;
}
}
this.crc32 = (int)crc.getValue();
this.crc32 = (int) crc.getValue();
zipInput.seek(0);
this.dataPosition = 0;
extraData = new byte[0];
setTime( new File(sourceDataFile).lastModified());
setTime(new File(sourceDataFile).lastModified());
}
public ZioEntry( String name, String sourceDataFile, short compression, int crc32, int compressedSize, int size)
throws IOException
{
zipInput = new ZipInput( sourceDataFile);
public ZioEntry(String name, String sourceDataFile, short compression, int crc32, int compressedSize, int size)
throws IOException {
zipInput = new ZipInput(sourceDataFile);
filename = name;
fileComment = "";
this.compression = compression;
@ -129,39 +127,35 @@ public class ZioEntry implements Cloneable {
this.size = size;
this.dataPosition = 0;
extraData = new byte[0];
setTime( new File(sourceDataFile).lastModified());
setTime(new File(sourceDataFile).lastModified());
}
// Return a copy with a new name
public ZioEntry getClonedEntry( String newName)
{
public ZioEntry getClonedEntry(String newName) {
ZioEntry clone;
try {
clone = (ZioEntry)this.clone();
}
catch (CloneNotSupportedException e)
{
clone = (ZioEntry) this.clone();
} catch (CloneNotSupportedException e) {
throw new IllegalStateException("clone() failed!");
}
clone.setName(newName);
return clone;
}
public void readLocalHeader() throws IOException
{
public void readLocalHeader() throws IOException {
ZipInput input = zipInput;
int tmp;
boolean debug = getLogger().isDebugEnabled();
input.seek( localHeaderOffset);
input.seek(localHeaderOffset);
if (debug) getLogger().debug( String.format("FILE POSITION: 0x%08x", input.getFilePointer()));
if (debug) getLogger().debug(String.format("FILE POSITION: 0x%08x", input.getFilePointer()));
// 0 4 Local file header signature = 0x04034b50
int signature = input.readInt();
if (signature != 0x04034b50) {
throw new IllegalStateException( String.format("Local header not found at pos=0x%08x, file=%s", input.getFilePointer(), filename));
throw new IllegalStateException(String.format("Local header not found at pos=0x%08x, file=%s", input.getFilePointer(), filename));
}
// This method is usually called just before the data read, so
@ -174,36 +168,44 @@ public class ZioEntry implements Cloneable {
short tmpShort;
// 4 2 Version needed to extract (minimum)
/* versionRequired */ tmpShort = input.readShort();
/* versionRequired */
tmpShort = input.readShort();
if (debug) log.debug(String.format("Version required: 0x%04x", tmpShort /*versionRequired*/));
// 6 2 General purpose bit flag
/* generalPurposeBits */ tmpShort = input.readShort();
/* generalPurposeBits */
tmpShort = input.readShort();
if (debug) log.debug(String.format("General purpose bits: 0x%04x", tmpShort /* generalPurposeBits */));
// 8 2 Compression method
/* compression */ tmpShort = input.readShort();
/* compression */
tmpShort = input.readShort();
if (debug) log.debug(String.format("Compression: 0x%04x", tmpShort /* compression */));
// 10 2 File last modification time
/* modificationTime */ tmpShort = input.readShort();
/* modificationTime */
tmpShort = input.readShort();
if (debug) log.debug(String.format("Modification time: 0x%04x", tmpShort /* modificationTime */));
// 12 2 File last modification date
/* modificationDate */ tmpShort = input.readShort();
/* modificationDate */
tmpShort = input.readShort();
if (debug) log.debug(String.format("Modification date: 0x%04x", tmpShort /* modificationDate */));
// 14 4 CRC-32
/* crc32 */ tmpInt = input.readInt();
/* crc32 */
tmpInt = input.readInt();
if (debug) log.debug(String.format("CRC-32: 0x%04x", tmpInt /*crc32*/));
// 18 4 Compressed size
/* compressedSize*/ tmpInt = input.readInt();
/* compressedSize*/
tmpInt = input.readInt();
if (debug) log.debug(String.format("Compressed size: 0x%04x", tmpInt /*compressedSize*/));
// 22 4 Uncompressed size
/* size */ tmpInt = input.readInt();
if (debug) log.debug(String.format("Size: 0x%04x", tmpInt /*size*/ ));
/* size */
tmpInt = input.readInt();
if (debug) log.debug(String.format("Size: 0x%04x", tmpInt /*size*/));
// 26 2 File name length (n)
short fileNameLen = input.readShort();
@ -218,46 +220,45 @@ public class ZioEntry implements Cloneable {
if (debug) log.debug("Filename: " + filename);
// Extra data
byte[] extra = input.readBytes( extraLen);
byte[] extra = input.readBytes(extraLen);
// Record the file position of this entry's data.
dataPosition = input.getFilePointer();
if (debug) log.debug(String.format("Data position: 0x%08x",dataPosition));
if (debug) log.debug(String.format("Data position: 0x%08x", dataPosition));
}
public void writeLocalEntry( ZipOutput output) throws IOException
{
public void writeLocalEntry(ZipOutput output) throws IOException {
if (data == null && dataPosition < 0 && zipInput != null) {
readLocalHeader();
}
localHeaderOffset = (int)output.getFilePointer();
localHeaderOffset = (int) output.getFilePointer();
boolean debug = getLogger().isDebugEnabled();
if (debug) {
getLogger().debug( String.format("Writing local header at 0x%08x - %s", localHeaderOffset, filename));
getLogger().debug(String.format("Writing local header at 0x%08x - %s", localHeaderOffset, filename));
}
if (entryOut != null) {
entryOut.close();
size = entryOut.getSize();
data = ((ByteArrayOutputStream)entryOut.getWrappedStream()).toByteArray();
data = ((ByteArrayOutputStream) entryOut.getWrappedStream()).toByteArray();
compressedSize = data.length;
crc32 = entryOut.getCRC();
}
output.writeInt( 0x04034b50);
output.writeShort( versionRequired);
output.writeShort( generalPurposeBits);
output.writeShort( compression);
output.writeShort( modificationTime);
output.writeShort( modificationDate);
output.writeInt( crc32);
output.writeInt( compressedSize);
output.writeInt( size);
output.writeShort( (short)filename.length());
output.writeInt(0x04034b50);
output.writeShort(versionRequired);
output.writeShort(generalPurposeBits);
output.writeShort(compression);
output.writeShort(modificationTime);
output.writeShort(modificationDate);
output.writeInt(crc32);
output.writeInt(compressedSize);
output.writeInt(size);
output.writeShort((short) filename.length());
numAlignBytes = 0;
@ -265,77 +266,74 @@ public class ZioEntry implements Cloneable {
if (compression == 0) {
long dataPos = output.getFilePointer() + // current position
2 + // plus size of extra data length
filename.length() + // plus filename
extraData.length; // plus extra data
2 + // plus size of extra data length
filename.length() + // plus filename
extraData.length; // plus extra data
short dataPosMod4 = (short)(dataPos % 4);
short dataPosMod4 = (short) (dataPos % 4);
if (dataPosMod4 > 0) {
numAlignBytes = (short)(4 - dataPosMod4);
numAlignBytes = (short) (4 - dataPosMod4);
}
}
// 28 2 Extra field length (m)
output.writeShort( (short)(extraData.length + numAlignBytes));
output.writeShort((short) (extraData.length + numAlignBytes));
// 30 n File name
output.writeString( filename);
output.writeString(filename);
// Extra data
output.writeBytes( extraData);
output.writeBytes(extraData);
// Zipalign bytes
if (numAlignBytes > 0) {
output.writeBytes( alignBytes, 0, numAlignBytes);
output.writeBytes(alignBytes, 0, numAlignBytes);
}
if (debug) getLogger().debug(String.format("Data position 0x%08x", output.getFilePointer()));
if (data != null) {
output.writeBytes( data);
output.writeBytes(data);
if (debug) getLogger().debug(String.format("Wrote %d bytes", data.length));
}
else {
} else {
if (debug) getLogger().debug(String.format("Seeking to position 0x%08x", dataPosition));
zipInput.seek( dataPosition);
zipInput.seek(dataPosition);
int bufferSize = Math.min( compressedSize, 8096);
int bufferSize = Math.min(compressedSize, 8096);
byte[] buffer = new byte[bufferSize];
long totalCount = 0;
while (totalCount != compressedSize) {
int numRead = zipInput.in.read( buffer, 0, (int)Math.min( compressedSize - totalCount, bufferSize));
int numRead = zipInput.in.read(buffer, 0, (int) Math.min(compressedSize - totalCount, bufferSize));
if (numRead > 0) {
output.writeBytes(buffer, 0, numRead);
if (debug) getLogger().debug(String.format("Wrote %d bytes", numRead));
totalCount += numRead;
}
else throw new IllegalStateException(String.format("EOF reached while copying %s with %d bytes left to go", filename, compressedSize - totalCount));
} else
throw new IllegalStateException(String.format("EOF reached while copying %s with %d bytes left to go", filename, compressedSize - totalCount));
}
}
}
public static ZioEntry read(ZipInput input) throws IOException
{
public static ZioEntry read(ZipInput input) throws IOException {
// 0 4 Central directory header signature = 0x02014b50
int signature = input.readInt();
if (signature != 0x02014b50) {
// back up to the signature
input.seek( input.getFilePointer() - 4);
input.seek(input.getFilePointer() - 4);
return null;
}
ZioEntry entry = new ZioEntry( input);
ZioEntry entry = new ZioEntry(input);
entry.doRead( input);
entry.doRead(input);
return entry;
}
private void doRead( ZipInput input) throws IOException
{
private void doRead(ZipInput input) throws IOException {
boolean debug = getLogger().isDebugEnabled();
@ -352,7 +350,7 @@ public class ZioEntry implements Cloneable {
if (debug) log.debug(String.format("General purpose bits: 0x%04x", generalPurposeBits));
// Bits 1, 2, 3, and 11 are allowed to be set (first bit is bit zero). Any others are a problem.
if ((generalPurposeBits & 0xF7F1) != 0x0000) {
throw new IllegalStateException("Can't handle general purpose bits == "+String.format("0x%04x",generalPurposeBits));
throw new IllegalStateException("Can't handle general purpose bits == " + String.format("0x%04x", generalPurposeBits));
}
// 8 2 Compression method
@ -406,12 +404,12 @@ public class ZioEntry implements Cloneable {
filename = input.readString(fileNameLen);
if (debug) log.debug("Filename: " + filename);
extraData = input.readBytes( extraLen);
extraData = input.readBytes(extraLen);
fileComment = input.readString( fileCommentLen);
fileComment = input.readString(fileCommentLen);
if (debug) log.debug("File comment: " + fileComment);
generalPurposeBits = (short)(generalPurposeBits & 0x0800); // Don't write a data descriptor, preserve UTF-8 encoded filename bit
generalPurposeBits = (short) (generalPurposeBits & 0x0800); // Don't write a data descriptor, preserve UTF-8 encoded filename bit
// Don't write zero-length entries with compression.
if (size == 0) {
@ -422,9 +420,10 @@ public class ZioEntry implements Cloneable {
}
/** Returns the entry's data. */
public byte[] getData() throws IOException
{
/**
* Returns the entry's data.
*/
public byte[] getData() throws IOException {
if (data != null) return data;
byte[] tmpdata = new byte[size];
@ -433,8 +432,9 @@ public class ZioEntry implements Cloneable {
int count = 0;
while (count != size) {
int numRead = din.read( tmpdata, count, size-count);
if (numRead < 0) throw new IllegalStateException(String.format("Read failed, expecting %d bytes, got %d instead", size, count));
int numRead = din.read(tmpdata, count, size - count);
if (numRead < 0)
throw new IllegalStateException(String.format("Read failed, expecting %d bytes, got %d instead", size, count));
count += numRead;
}
return tmpdata;
@ -451,66 +451,64 @@ public class ZioEntry implements Cloneable {
if (entryOut != null) {
entryOut.close();
size = entryOut.getSize();
data = ((ByteArrayOutputStream)entryOut.getWrappedStream()).toByteArray();
data = ((ByteArrayOutputStream) entryOut.getWrappedStream()).toByteArray();
compressedSize = data.length;
crc32 = entryOut.getCRC();
entryOut = null;
InputStream rawis = new ByteArrayInputStream( data);
InputStream rawis = new ByteArrayInputStream(data);
if (compression == 0) return rawis;
else {
// Hacky, inflate using a sequence of input streams that returns 1 byte more than the actual length of the data.
// This extra dummy byte is required by InflaterInputStream when the data doesn't have the header and crc fields (as it is in zip files).
return new InflaterInputStream( new SequenceInputStream(rawis, new ByteArrayInputStream(new byte[1])), new Inflater( true));
return new InflaterInputStream(new SequenceInputStream(rawis, new ByteArrayInputStream(new byte[1])), new Inflater(true));
}
}
ZioEntryInputStream dataStream;
dataStream = new ZioEntryInputStream(this);
if (monitorStream != null) dataStream.setMonitorStream( monitorStream);
if (compression != 0) {
if (monitorStream != null) dataStream.setMonitorStream(monitorStream);
if (compression != 0) {
// Note: When using nowrap=true with Inflater it is also necessary to provide
// an extra "dummy" byte as input. This is required by the ZLIB native library
// in order to support certain optimizations.
dataStream.setReturnDummyByte(true);
return new InflaterInputStream( dataStream, new Inflater( true));
}
else return dataStream;
return new InflaterInputStream(dataStream, new Inflater(true));
} else return dataStream;
}
// Returns an output stream for writing an entry's data.
public OutputStream getOutputStream()
{
entryOut = new ZioEntryOutputStream( compression, new ByteArrayOutputStream());
public OutputStream getOutputStream() {
entryOut = new ZioEntryOutputStream(compression, new ByteArrayOutputStream());
return entryOut;
}
public void write( ZipOutput output) throws IOException {
public void write(ZipOutput output) throws IOException {
boolean debug = getLogger().isDebugEnabled();
output.writeInt( 0x02014b50);
output.writeShort( versionMadeBy);
output.writeShort( versionRequired);
output.writeShort( generalPurposeBits);
output.writeShort( compression);
output.writeShort( modificationTime);
output.writeShort( modificationDate);
output.writeInt( crc32);
output.writeInt( compressedSize);
output.writeInt( size);
output.writeShort( (short)filename.length());
output.writeShort( (short)(extraData.length + numAlignBytes));
output.writeShort( (short)fileComment.length());
output.writeShort( diskNumberStart);
output.writeShort( internalAttributes);
output.writeInt( externalAttributes);
output.writeInt( localHeaderOffset);
output.writeInt(0x02014b50);
output.writeShort(versionMadeBy);
output.writeShort(versionRequired);
output.writeShort(generalPurposeBits);
output.writeShort(compression);
output.writeShort(modificationTime);
output.writeShort(modificationDate);
output.writeInt(crc32);
output.writeInt(compressedSize);
output.writeInt(size);
output.writeShort((short) filename.length());
output.writeShort((short) (extraData.length + numAlignBytes));
output.writeShort((short) fileComment.length());
output.writeShort(diskNumberStart);
output.writeShort(internalAttributes);
output.writeInt(externalAttributes);
output.writeInt(localHeaderOffset);
output.writeString( filename);
output.writeBytes( extraData);
if (numAlignBytes > 0) output.writeBytes( alignBytes, 0, numAlignBytes);
output.writeString( fileComment);
output.writeString(filename);
output.writeBytes(extraData);
if (numAlignBytes > 0) output.writeBytes(alignBytes, 0, numAlignBytes);
output.writeString(fileComment);
}
@ -518,13 +516,13 @@ public class ZioEntry implements Cloneable {
* Returns timetamp in Java format
*/
public long getTime() {
int year = (int)(((modificationDate >> 9) & 0x007f) + 80);
int month = (int)(((modificationDate >> 5) & 0x000f) - 1);
int day = (int)(modificationDate & 0x001f);
int hour = (int)((modificationTime >> 11) & 0x001f);
int minute = (int)((modificationTime >> 5) & 0x003f);
int seconds = (int)((modificationTime << 1) & 0x003e);
Date d = new Date( year, month, day, hour, minute, seconds);
int year = (int) (((modificationDate >> 9) & 0x007f) + 80);
int month = (int) (((modificationDate >> 5) & 0x000f) - 1);
int day = (int) (modificationDate & 0x001f);
int hour = (int) ((modificationTime >> 11) & 0x001f);
int minute = (int) ((modificationTime >> 5) & 0x003f);
int seconds = (int) ((modificationTime << 1) & 0x003e);
Date d = new Date(year, month, day, hour, minute, seconds);
return d.getTime();
}
@ -537,15 +535,14 @@ public class ZioEntry implements Cloneable {
int year = d.getYear() + 1900;
if (year < 1980) {
dtime = (1 << 21) | (1 << 16);
}
else {
} else {
dtime = (year - 1980) << 25 | (d.getMonth() + 1) << 21 |
d.getDate() << 16 | d.getHours() << 11 | d.getMinutes() << 5 |
d.getSeconds() >> 1;
d.getDate() << 16 | d.getHours() << 11 | d.getMinutes() << 5 |
d.getSeconds() >> 1;
}
modificationDate = (short)(dtime >> 16);
modificationTime = (short)(dtime & 0xFFFF);
modificationDate = (short) (dtime >> 16);
modificationTime = (short) (dtime & 0xFFFF);
}
public boolean isDirectory() {
@ -556,13 +553,15 @@ public class ZioEntry implements Cloneable {
return filename;
}
public void setName( String filename) {
public void setName(String filename) {
this.filename = filename;
}
/** Use 0 (STORED), or 8 (DEFLATE). */
public void setCompression( int compression) {
this.compression = (short)compression;
/**
* Use 0 (STORED), or 8 (DEFLATE).
*/
public void setCompression(int compression) {
this.compression = (short) compression;
}
public short getVersionMadeBy() {

View File

@ -13,18 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.zipio;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
/** Input stream used to read just the data from a zip file entry. */
/**
* Input stream used to read just the data from a zip file entry.
*/
public class ZioEntryInputStream extends InputStream {
RandomAccessFile raf;
@ -35,9 +38,9 @@ public class ZioEntryInputStream extends InputStream {
boolean returnDummyByte = false;
OutputStream monitor = null;
public ZioEntryInputStream( ZioEntry entry) throws IOException {
public ZioEntryInputStream(ZioEntry entry) throws IOException {
log = LoggerManager.getLogger( this.getClass().getName());
log = LoggerManager.getLogger(this.getClass().getName());
debug = log.isDebugEnabled();
offset = 0;
size = entry.getCompressedSize();
@ -45,9 +48,8 @@ public class ZioEntryInputStream extends InputStream {
long dpos = entry.getDataPosition();
if (dpos >= 0) {
if (debug) log.debug(String.format("Seeking to %d", entry.getDataPosition()));
raf.seek( entry.getDataPosition());
}
else {
raf.seek(entry.getDataPosition());
} else {
// seeks to, then reads, the local header, causing the
// file pointer to be positioned at the start of the data.
entry.readLocalHeader();
@ -55,7 +57,7 @@ public class ZioEntryInputStream extends InputStream {
}
public void setReturnDummyByte( boolean returnExtraByte) {
public void setReturnDummyByte(boolean returnExtraByte) {
returnDummyByte = returnExtraByte;
}
@ -87,22 +89,20 @@ public class ZioEntryInputStream extends InputStream {
if (returnDummyByte) {
returnDummyByte = false;
return 0;
}
else return -1;
} else return -1;
}
int b = raf.read();
if (b >= 0) {
if (monitor != null) monitor.write(b);
if (debug) log.debug("Read 1 byte");
offset += 1;
}
else if (debug) log.debug("Read 0 bytes");
} else if (debug) log.debug("Read 0 bytes");
return b;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return readBytes( b, off, len);
return readBytes(b, off, len);
}
private int readBytes(byte[] b, int off, int len) throws IOException {
@ -111,10 +111,9 @@ public class ZioEntryInputStream extends InputStream {
returnDummyByte = false;
b[off] = 0;
return 1;
}
else return -1;
} else return -1;
}
int numToRead = Math.min( len, available());
int numToRead = Math.min(len, available());
int numRead = raf.read(b, off, numToRead);
if (numRead > 0) {
if (monitor != null) monitor.write(b, off, numRead);
@ -126,13 +125,13 @@ public class ZioEntryInputStream extends InputStream {
@Override
public int read(byte[] b) throws IOException {
return readBytes( b, 0, b.length);
return readBytes(b, 0, b.length);
}
@Override
public long skip(long n) throws IOException {
long numToSkip = Math.min( n, available());
raf.seek( raf.getFilePointer() + numToSkip);
long numToSkip = Math.min(n, available());
raf.seek(raf.getFilePointer() + numToSkip);
if (debug) log.debug(String.format("Skipped %d bytes", numToSkip));
return numToSkip;
}

View File

@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.zipio;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.CRC32;
@ -29,18 +29,17 @@ public class ZioEntryOutputStream extends OutputStream {
OutputStream wrapped;
OutputStream downstream;
public ZioEntryOutputStream( int compression, OutputStream wrapped)
{
public ZioEntryOutputStream(int compression, OutputStream wrapped) {
this.wrapped = wrapped;
if (compression != 0)
downstream = new DeflaterOutputStream( wrapped, new Deflater( Deflater.BEST_COMPRESSION, true));
downstream = new DeflaterOutputStream(wrapped, new Deflater(Deflater.BEST_COMPRESSION, true));
else downstream = wrapped;
}
public void close() throws IOException {
downstream.flush();
downstream.close();
crcValue = (int)crc.getValue();
crcValue = (int) crc.getValue();
}
public int getCRC() {
@ -58,14 +57,14 @@ public class ZioEntryOutputStream extends OutputStream {
}
public void write(byte[] b, int off, int len) throws IOException {
downstream.write( b, off, len);
crc.update( b, off, len);
downstream.write(b, off, len);
crc.update(b, off, len);
size += len;
}
public void write(int b) throws IOException {
downstream.write( b);
crc.update( b);
downstream.write(b);
crc.update(b);
size += 1;
}
@ -73,8 +72,7 @@ public class ZioEntryOutputStream extends OutputStream {
return size;
}
public OutputStream getWrappedStream()
{
public OutputStream getWrappedStream() {
return wrapped;
}

View File

@ -13,16 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.zipio;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
@ -31,15 +32,10 @@ import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
/**
*
*/
public class ZipInput implements Closeable
{
public class ZipInput implements Closeable {
static LoggerInterface log;
public String inputFilename;
@ -47,14 +43,13 @@ public class ZipInput implements Closeable
long fileLength;
int scanIterations = 0;
Map<String,ZioEntry> zioEntries = new LinkedHashMap<String,ZioEntry>();
Map<String, ZioEntry> zioEntries = new LinkedHashMap<String, ZioEntry>();
CentralEnd centralEnd;
Manifest manifest;
public ZipInput( String filename) throws IOException
{
public ZipInput(String filename) throws IOException {
this.inputFilename = filename;
in = new RandomAccessFile( new File( inputFilename), "r");
in = new RandomAccessFile(new File(inputFilename), "r");
fileLength = in.length();
}
@ -71,32 +66,32 @@ public class ZipInput implements Closeable
return fileLength;
}
public static ZipInput read( String filename) throws IOException {
ZipInput zipInput = new ZipInput( filename);
public static ZipInput read(String filename) throws IOException {
ZipInput zipInput = new ZipInput(filename);
zipInput.doRead();
return zipInput;
}
public ZioEntry getEntry( String filename) {
public ZioEntry getEntry(String filename) {
return zioEntries.get(filename);
}
public Map<String,ZioEntry> getEntries() {
public Map<String, ZioEntry> getEntries() {
return zioEntries;
}
/** Returns the names of immediate children in the directory with the given name.
* The path value must end with a "/" character. Use a value of "/"
* to get the root entries.
/**
* Returns the names of immediate children in the directory with the given name.
* The path value must end with a "/" character. Use a value of "/"
* to get the root entries.
*/
public Collection<String> list(String path)
{
public Collection<String> list(String path) {
if (!path.endsWith("/")) throw new IllegalArgumentException("Invalid path -- does not end with '/'");
if (path.startsWith("/")) path = path.substring(1);
Pattern p = Pattern.compile( String.format("^%s([^/]+/?).*", path));
Pattern p = Pattern.compile(String.format("^%s([^/]+/?).*", path));
Set<String> names = new TreeSet<String>();
@ -111,79 +106,82 @@ public class ZipInput implements Closeable
if (manifest == null) {
ZioEntry e = zioEntries.get("META-INF/MANIFEST.MF");
if (e != null) {
manifest = new Manifest( e.getInputStream());
manifest = new Manifest(e.getInputStream());
}
}
return manifest;
}
/** Scan the end of the file for the end of central directory record (EOCDR).
Returns the file offset of the EOCD signature. The size parameter is an
initial buffer size (e.g., 256).
/**
* Scan the end of the file for the end of central directory record (EOCDR).
* Returns the file offset of the EOCD signature. The size parameter is an
* initial buffer size (e.g., 256).
*/
public long scanForEOCDR( int size) throws IOException {
if (size > fileLength || size > 65536) throw new IllegalStateException( "End of central directory not found in " + inputFilename);
public long scanForEOCDR(int size) throws IOException {
if (size > fileLength || size > 65536)
throw new IllegalStateException("End of central directory not found in " + inputFilename);
int scanSize = (int)Math.min( fileLength, size);
int scanSize = (int) Math.min(fileLength, size);
byte[] scanBuf = new byte[scanSize];
in.seek( fileLength - scanSize);
in.seek(fileLength - scanSize);
in.readFully( scanBuf);
in.readFully(scanBuf);
for (int i = scanSize - 22; i >= 0; i--) {
scanIterations += 1;
if (scanBuf[i] == 0x50 && scanBuf[i+1] == 0x4b && scanBuf[i+2] == 0x05 && scanBuf[i+3] == 0x06) {
if (scanBuf[i] == 0x50 && scanBuf[i + 1] == 0x4b && scanBuf[i + 2] == 0x05 && scanBuf[i + 3] == 0x06) {
return fileLength - scanSize + i;
}
}
return scanForEOCDR( size * 2);
return scanForEOCDR(size * 2);
}
private void doRead()
{
private void doRead() {
try {
long posEOCDR = scanForEOCDR( 256);
in.seek( posEOCDR);
centralEnd = CentralEnd.read( this);
long posEOCDR = scanForEOCDR(256);
in.seek(posEOCDR);
centralEnd = CentralEnd.read(this);
boolean debug = getLogger().isDebugEnabled();
if (debug) {
getLogger().debug(String.format("EOCD found in %d iterations", scanIterations));
getLogger().debug(String.format("Directory entries=%d, size=%d, offset=%d/0x%08x", centralEnd.totalCentralEntries,
centralEnd.centralDirectorySize, centralEnd.centralStartOffset, centralEnd.centralStartOffset));
centralEnd.centralDirectorySize, centralEnd.centralStartOffset, centralEnd.centralStartOffset));
ZipListingHelper.listHeader( getLogger());
ZipListingHelper.listHeader(getLogger());
}
in.seek( centralEnd.centralStartOffset);
in.seek(centralEnd.centralStartOffset);
for (int i = 0; i < centralEnd.totalCentralEntries; i++) {
ZioEntry entry = ZioEntry.read(this);
zioEntries.put( entry.getName(), entry);
if (debug) ZipListingHelper.listEntry( getLogger(), entry);
zioEntries.put(entry.getName(), entry);
if (debug) ZipListingHelper.listEntry(getLogger(), entry);
}
}
catch (Throwable t) {
} catch (Throwable t) {
t.printStackTrace();
}
}
@Override
public void close() {
if (in != null) try { in.close(); } catch( Throwable t) {}
if (in != null) try {
in.close();
} catch (Throwable t) {
}
}
public long getFilePointer() throws IOException {
return in.getFilePointer();
}
public void seek( long position) throws IOException {
public void seek(long position) throws IOException {
in.seek(position);
}
@ -191,7 +189,7 @@ public class ZipInput implements Closeable
return in.readByte();
}
public int readInt() throws IOException{
public int readInt() throws IOException {
int result = 0;
for (int i = 0; i < 4; i++) {
result |= (in.readUnsignedByte() << (8 * i));
@ -207,7 +205,7 @@ public class ZipInput implements Closeable
return result;
}
public String readString( int length) throws IOException {
public String readString(int length) throws IOException {
byte[] buffer = new byte[length];
for (int i = 0; i < length; i++) {
@ -216,7 +214,7 @@ public class ZipInput implements Closeable
return new String(buffer);
}
public byte[] readBytes( int length) throws IOException {
public byte[] readBytes(int length) throws IOException {
byte[] buffer = new byte[length];
for (int i = 0; i < length; i++) {
@ -225,8 +223,8 @@ public class ZipInput implements Closeable
return buffer;
}
public int read( byte[] b, int offset, int length) throws IOException {
return in.read( b, offset, length);
public int read(byte[] b, int offset, int length) throws IOException {
return in.read(b, offset, length);
}
}

View File

@ -13,41 +13,39 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.zipio;
import java.util.Date;
import kellinwood.logging.LoggerInterface;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
import java.util.Date;
/**
*
*/
public class ZipListingHelper
{
public class ZipListingHelper {
static DateFormat dateFormat = new SimpleDateFormat("MM-dd-yy HH:mm");
public static void listHeader(LoggerInterface log)
{
public static void listHeader(LoggerInterface log) {
log.debug(" Length Method Size Ratio Date Time CRC-32 Name");
log.debug("-------- ------ ------- ----- ---- ---- ------ ----");
}
public static void listEntry(LoggerInterface log, ZioEntry entry)
{
public static void listEntry(LoggerInterface log, ZioEntry entry) {
int ratio = 0;
if (entry.getSize() > 0) ratio = (100 * (entry.getSize() - entry.getCompressedSize())) / entry.getSize();
log.debug(String.format("%8d %6s %8d %4d%% %s %08x %s",
entry.getSize(),
entry.getCompression() == 0 ? "Stored" : "Defl:N",
entry.getCompressedSize(),
ratio,
dateFormat.format( new Date( entry.getTime())),
entry.getCrc32(),
entry.getName()));
entry.getSize(),
entry.getCompression() == 0 ? "Stored" : "Defl:N",
entry.getCompressedSize(),
ratio,
dateFormat.format(new Date(entry.getTime())),
entry.getCrc32(),
entry.getName()));
}
}

View File

@ -13,24 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.zipio;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.LinkedList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
/**
*
*/
public class ZipOutput
{
public class ZipOutput {
static LoggerInterface log;
@ -41,30 +42,26 @@ public class ZipOutput
List<ZioEntry> entriesWritten = new LinkedList<ZioEntry>();
Set<String> namesWritten = new HashSet<String>();
public ZipOutput( String filename) throws IOException
{
public ZipOutput(String filename) throws IOException {
this.outputFilename = filename;
File ofile = new File( outputFilename);
File ofile = new File(outputFilename);
init(ofile);
}
public ZipOutput( File outputFile) throws IOException
{
public ZipOutput(File outputFile) throws IOException {
this.outputFilename = outputFile.getAbsolutePath();
File ofile = outputFile;
init(ofile);
}
private void init( File ofile) throws IOException
{
private void init(File ofile) throws IOException {
if (ofile.exists()) ofile.delete();
out = new FileOutputStream( ofile);
if (getLogger().isDebugEnabled()) ZipListingHelper.listHeader( getLogger());
out = new FileOutputStream(ofile);
if (getLogger().isDebugEnabled()) ZipListingHelper.listHeader(getLogger());
}
public ZipOutput( OutputStream os) throws IOException
{
public ZipOutput(OutputStream os) throws IOException {
out = os;
}
@ -73,38 +70,39 @@ public class ZipOutput
return log;
}
public void write( ZioEntry entry) throws IOException {
public void write(ZioEntry entry) throws IOException {
String entryName = entry.getName();
if (namesWritten.contains( entryName)) {
if (namesWritten.contains(entryName)) {
getLogger().warning("Skipping duplicate file in output: " + entryName);
return;
}
entry.writeLocalEntry( this);
entriesWritten.add( entry);
namesWritten.add( entryName);
if (getLogger().isDebugEnabled()) ZipListingHelper.listEntry( getLogger(), entry);
entry.writeLocalEntry(this);
entriesWritten.add(entry);
namesWritten.add(entryName);
if (getLogger().isDebugEnabled()) ZipListingHelper.listEntry(getLogger(), entry);
}
public void close() throws IOException
{
public void close() throws IOException {
CentralEnd centralEnd = new CentralEnd();
centralEnd.centralStartOffset = (int)getFilePointer();
centralEnd.numCentralEntries = centralEnd.totalCentralEntries = (short)entriesWritten.size();
centralEnd.centralStartOffset = (int) getFilePointer();
centralEnd.numCentralEntries = centralEnd.totalCentralEntries = (short) entriesWritten.size();
for (ZioEntry entry : entriesWritten) {
entry.write( this);
entry.write(this);
}
centralEnd.centralDirectorySize = (int)(getFilePointer() - centralEnd.centralStartOffset);
centralEnd.centralDirectorySize = (int) (getFilePointer() - centralEnd.centralStartOffset);
centralEnd.fileComment = "";
centralEnd.write( this);
centralEnd.write(this);
if (out != null) try { out.close(); } catch( Throwable t) {}
if (out != null) try {
out.close();
} catch (Throwable t) {
}
}
public int getFilePointer() throws IOException {
@ -112,42 +110,42 @@ public class ZipOutput
}
public void writeInt( int value) throws IOException{
public void writeInt(int value) throws IOException {
byte[] data = new byte[4];
for (int i = 0; i < 4; i++) {
data[i] = (byte)(value & 0xFF);
data[i] = (byte) (value & 0xFF);
value = value >> 8;
}
out.write( data);
out.write(data);
filePointer += 4;
}
public void writeShort( short value) throws IOException {
public void writeShort(short value) throws IOException {
byte[] data = new byte[2];
for (int i = 0; i < 2; i++) {
data[i] = (byte)(value & 0xFF);
value = (short)(value >> 8);
data[i] = (byte) (value & 0xFF);
value = (short) (value >> 8);
}
out.write( data);
out.write(data);
filePointer += 2;
}
public void writeString( String value) throws IOException {
public void writeString(String value) throws IOException {
byte[] data = value.getBytes();
out.write( data);
out.write(data);
filePointer += data.length;
}
public void writeBytes( byte[] value) throws IOException {
public void writeBytes(byte[] value) throws IOException {
out.write( value);
out.write(value);
filePointer += value.length;
}
public void writeBytes( byte[] value, int offset, int length) throws IOException {
public void writeBytes(byte[] value, int offset, int length) throws IOException {
out.write( value, offset, length);
out.write(value, offset, length);
filePointer += length;
}