reformat all zipsigner code with Android Studio Ctrl-Alt-L
This commit is contained in:
parent
a3d9850a42
commit
4e4dd2385b
@ -13,67 +13,66 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kellinwood.logging;
|
package kellinwood.logging;
|
||||||
|
|
||||||
import java.io.PrintStream;
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
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) {
|
public AbstractLogger(String category) {
|
||||||
this.category = category;
|
this.category = category;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String format( String level, String message) {
|
protected String format(String level, String message) {
|
||||||
return String.format( "%s %s %s: %s\n", dateFormat.format(new Date()), level, category, 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 (message == null) {
|
||||||
if (t != null) message = t.getClass().getName();
|
if (t != null) message = t.getClass().getName();
|
||||||
else message = "null";
|
else message = "null";
|
||||||
}
|
}
|
||||||
write( level, message, t);
|
write(level, message, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void debug(String message, Throwable t) {
|
public void debug(String message, Throwable t) {
|
||||||
writeFixNullMessage( DEBUG, message, t);
|
writeFixNullMessage(DEBUG, message, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void debug(String message) {
|
public void debug(String message) {
|
||||||
writeFixNullMessage( DEBUG, message, null);
|
writeFixNullMessage(DEBUG, message, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void error(String message, Throwable t) {
|
public void error(String message, Throwable t) {
|
||||||
writeFixNullMessage( ERROR, message, t);
|
writeFixNullMessage(ERROR, message, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void error(String message) {
|
public void error(String message) {
|
||||||
writeFixNullMessage( ERROR, message, null);
|
writeFixNullMessage(ERROR, message, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void info(String message, Throwable t) {
|
public void info(String message, Throwable t) {
|
||||||
writeFixNullMessage( INFO, message, t);
|
writeFixNullMessage(INFO, message, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void info(String message) {
|
public void info(String message) {
|
||||||
writeFixNullMessage( INFO, message, null);
|
writeFixNullMessage(INFO, message, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void warning(String message, Throwable t) {
|
public void warning(String message, Throwable t) {
|
||||||
writeFixNullMessage( WARNING, message, t);
|
writeFixNullMessage(WARNING, message, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void warning(String message) {
|
public void warning(String message) {
|
||||||
writeFixNullMessage( WARNING, message, null);
|
writeFixNullMessage(WARNING, message, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDebugEnabled() {
|
public boolean isDebugEnabled() {
|
||||||
@ -91,6 +90,4 @@ public abstract class AbstractLogger implements LoggerInterface
|
|||||||
public boolean isWarningEnabled() {
|
public boolean isWarningEnabled() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,11 +13,12 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kellinwood.logging;
|
package kellinwood.logging;
|
||||||
|
|
||||||
public class ConsoleLoggerFactory implements LoggerFactory {
|
public class ConsoleLoggerFactory implements LoggerFactory {
|
||||||
|
|
||||||
public LoggerInterface getLogger(String category) {
|
public LoggerInterface getLogger(String category) {
|
||||||
return new StreamLogger( category, System.out);
|
return new StreamLogger(category, System.out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,10 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kellinwood.logging;
|
package kellinwood.logging;
|
||||||
|
|
||||||
public interface LoggerFactory {
|
public interface LoggerFactory {
|
||||||
|
|
||||||
public LoggerInterface getLogger( String category);
|
public LoggerInterface getLogger(String category);
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kellinwood.logging;
|
package kellinwood.logging;
|
||||||
|
|
||||||
public interface LoggerInterface {
|
public interface LoggerInterface {
|
||||||
@ -23,21 +24,29 @@ public interface LoggerInterface {
|
|||||||
public static final String DEBUG = "DEBUG";
|
public static final String DEBUG = "DEBUG";
|
||||||
|
|
||||||
public boolean isErrorEnabled();
|
public boolean isErrorEnabled();
|
||||||
public void error( String message);
|
|
||||||
public void error( String message, Throwable t);
|
public void error(String message);
|
||||||
|
|
||||||
|
public void error(String message, Throwable t);
|
||||||
|
|
||||||
|
|
||||||
public boolean isWarningEnabled();
|
public boolean isWarningEnabled();
|
||||||
public void warning( String message);
|
|
||||||
public void warning( String message, Throwable t);
|
public void warning(String message);
|
||||||
|
|
||||||
|
public void warning(String message, Throwable t);
|
||||||
|
|
||||||
public boolean isInfoEnabled();
|
public boolean isInfoEnabled();
|
||||||
public void info( String message);
|
|
||||||
public void info( String message, Throwable t);
|
public void info(String message);
|
||||||
|
|
||||||
|
public void info(String message, Throwable t);
|
||||||
|
|
||||||
public boolean isDebugEnabled();
|
public boolean isDebugEnabled();
|
||||||
public void debug( String message);
|
|
||||||
public void debug( String message, Throwable t);
|
public void debug(String message);
|
||||||
|
|
||||||
|
public void debug(String message, Throwable t);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kellinwood.logging;
|
package kellinwood.logging;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -22,18 +23,18 @@ 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) {
|
public static void setLoggerFactory(LoggerFactory f) {
|
||||||
factory = f;
|
factory = f;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoggerInterface getLogger(String category) {
|
public static LoggerInterface getLogger(String category) {
|
||||||
|
|
||||||
LoggerInterface logger = loggers.get( category);
|
LoggerInterface logger = loggers.get(category);
|
||||||
if (logger == null) {
|
if (logger == null) {
|
||||||
logger = factory.getLogger(category);
|
logger = factory.getLogger(category);
|
||||||
loggers.put( category, logger);
|
loggers.put(category, logger);
|
||||||
}
|
}
|
||||||
return logger;
|
return logger;
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kellinwood.logging;
|
package kellinwood.logging;
|
||||||
|
|
||||||
public class NullLoggerFactory implements LoggerFactory {
|
public class NullLoggerFactory implements LoggerFactory {
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kellinwood.logging;
|
package kellinwood.logging;
|
||||||
|
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
@ -21,15 +22,14 @@ public class StreamLogger extends AbstractLogger {
|
|||||||
|
|
||||||
PrintStream out;
|
PrintStream out;
|
||||||
|
|
||||||
public StreamLogger( String category, PrintStream out)
|
public StreamLogger(String category, PrintStream out) {
|
||||||
{
|
super(category);
|
||||||
super( category);
|
|
||||||
this.out = out;
|
this.out = out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void write(String level, String message, Throwable t) {
|
protected void write(String level, String message, Throwable t) {
|
||||||
out.print( format( level, message));
|
out.print(format(level, message));
|
||||||
if (t != null) t.printStackTrace(out);
|
if (t != null) t.printStackTrace(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
|
|
||||||
package kellinwood.security.zipsigner;
|
package kellinwood.security.zipsigner;
|
||||||
|
|
||||||
public class AutoKeyException extends RuntimeException {
|
public class AutoKeyException extends RuntimeException {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
public AutoKeyException( String message) {
|
public AutoKeyException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AutoKeyException( String message, Throwable cause) {
|
public AutoKeyException(String message, Throwable cause) {
|
||||||
super(message, cause);
|
super(message, cause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,15 +15,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package kellinwood.security.zipsigner;
|
package kellinwood.security.zipsigner;
|
||||||
|
|
||||||
|
import kellinwood.logging.LoggerInterface;
|
||||||
|
import kellinwood.logging.LoggerManager;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
import kellinwood.logging.LoggerInterface;
|
|
||||||
import kellinwood.logging.LoggerManager;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This class provides Base64 encoding services using one of several possible
|
* This class provides Base64 encoding services using one of several possible
|
||||||
* implementations available elsewhere in the classpath. Supported implementations
|
* implementations available elsewhere in the classpath. Supported implementations
|
||||||
@ -55,16 +56,16 @@ public class Base64 {
|
|||||||
|
|
||||||
Class<Object> clazz;
|
Class<Object> clazz;
|
||||||
|
|
||||||
logger = LoggerManager.getLogger( Base64.class.getName());
|
logger = LoggerManager.getLogger(Base64.class.getName());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
clazz = (Class<Object>) Class.forName("android.util.Base64");
|
clazz = (Class<Object>) Class.forName("android.util.Base64");
|
||||||
// Looking for encode( byte[] input, int flags)
|
// Looking for encode( byte[] input, int flags)
|
||||||
aEncodeMethod = clazz.getMethod("encode", byte[].class, Integer.TYPE);
|
aEncodeMethod = clazz.getMethod("encode", byte[].class, Integer.TYPE);
|
||||||
aDecodeMethod = clazz.getMethod("decode", byte[].class, Integer.TYPE);
|
aDecodeMethod = clazz.getMethod("decode", byte[].class, Integer.TYPE);
|
||||||
logger.info( clazz.getName() + " is available.");
|
logger.info(clazz.getName() + " is available.");
|
||||||
}
|
} catch (ClassNotFoundException x) {
|
||||||
catch (ClassNotFoundException x) {} // Ignore
|
} // Ignore
|
||||||
catch (Exception x) {
|
catch (Exception x) {
|
||||||
logger.error("Failed to initialize use of android.util.Base64", x);
|
logger.error("Failed to initialize use of android.util.Base64", x);
|
||||||
}
|
}
|
||||||
@ -74,12 +75,12 @@ public class Base64 {
|
|||||||
bEncoder = clazz.newInstance();
|
bEncoder = clazz.newInstance();
|
||||||
// Looking for encode( byte[] input, int offset, int length, OutputStream output)
|
// Looking for encode( byte[] input, int offset, int length, OutputStream output)
|
||||||
bEncodeMethod = clazz.getMethod("encode", byte[].class, Integer.TYPE, Integer.TYPE, OutputStream.class);
|
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)
|
// Looking for decode( byte[] input, int offset, int length, OutputStream output)
|
||||||
bDecodeMethod = clazz.getMethod("decode", byte[].class, Integer.TYPE, Integer.TYPE, OutputStream.class);
|
bDecodeMethod = clazz.getMethod("decode", byte[].class, Integer.TYPE, Integer.TYPE, OutputStream.class);
|
||||||
|
|
||||||
}
|
} catch (ClassNotFoundException x) {
|
||||||
catch (ClassNotFoundException x) {} // Ignore
|
} // Ignore
|
||||||
catch (Exception x) {
|
catch (Exception x) {
|
||||||
logger.error("Failed to initialize use of org.bouncycastle.util.encoders.Base64Encoder", 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 {
|
try {
|
||||||
if (aEncodeMethod != null) {
|
if (aEncodeMethod != null) {
|
||||||
// Invoking a static method call, using null for the instance value
|
// Invoking a static method call, using null for the instance value
|
||||||
byte[] encodedBytes = (byte[])aEncodeMethod.invoke(null, data, 2);
|
byte[] encodedBytes = (byte[]) aEncodeMethod.invoke(null, data, 2);
|
||||||
return new String( encodedBytes);
|
return new String(encodedBytes);
|
||||||
}
|
} else if (bEncodeMethod != null) {
|
||||||
else if (bEncodeMethod != null) {
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
bEncodeMethod.invoke(bEncoder, data, 0, data.length, baos);
|
bEncodeMethod.invoke(bEncoder, data, 0, data.length, baos);
|
||||||
return new String( baos.toByteArray());
|
return new String(baos.toByteArray());
|
||||||
}
|
}
|
||||||
}
|
} catch (Exception x) {
|
||||||
catch (Exception x) {
|
throw new IllegalStateException(x.getClass().getName() + ": " + x.getMessage());
|
||||||
throw new IllegalStateException( x.getClass().getName() + ": " + x.getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
throw new IllegalStateException("No base64 encoder implementation is available.");
|
throw new IllegalStateException("No base64 encoder implementation is available.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] decode( byte[] data) {
|
public static byte[] decode(byte[] data) {
|
||||||
try {
|
try {
|
||||||
if (aDecodeMethod != null) {
|
if (aDecodeMethod != null) {
|
||||||
// Invoking a static method call, using null for the instance value
|
// 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;
|
return decodedBytes;
|
||||||
}
|
} else if (bDecodeMethod != null) {
|
||||||
else if (bDecodeMethod != null) {
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
bDecodeMethod.invoke(bEncoder, data, 0, data.length, baos);
|
bDecodeMethod.invoke(bEncoder, data, 0, data.length, baos);
|
||||||
return baos.toByteArray();
|
return baos.toByteArray();
|
||||||
}
|
}
|
||||||
}
|
} catch (Exception x) {
|
||||||
catch (Exception x) {
|
throw new IllegalStateException(x.getClass().getName() + ": " + x.getMessage());
|
||||||
throw new IllegalStateException( x.getClass().getName() + ": " + x.getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
package kellinwood.security.zipsigner;
|
package kellinwood.security.zipsigner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,58 +15,58 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package kellinwood.security.zipsigner;
|
package kellinwood.security.zipsigner;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
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.
|
* section (16 bytes per row) and right-column printable character dislpay.
|
||||||
*/
|
*/
|
||||||
public class HexDumpEncoder
|
public class HexDumpEncoder {
|
||||||
{
|
|
||||||
|
|
||||||
static HexEncoder encoder = new HexEncoder();
|
static HexEncoder encoder = new HexEncoder();
|
||||||
|
|
||||||
public static String encode( byte[] data) {
|
public static String encode(byte[] data) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
encoder.encode( data, 0, data.length, baos);
|
encoder.encode(data, 0, data.length, baos);
|
||||||
byte[] hex = baos.toByteArray();
|
byte[] hex = baos.toByteArray();
|
||||||
|
|
||||||
StringBuilder hexDumpOut = new StringBuilder();
|
StringBuilder hexDumpOut = new StringBuilder();
|
||||||
for (int i = 0; i < hex.length; i += 32) {
|
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 hexOut = new StringBuilder();
|
||||||
StringBuilder chrOut = 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) {
|
for (int j = i; j < max; j += 2) {
|
||||||
hexOut.append( Character.valueOf( (char)hex[j]));
|
hexOut.append(Character.valueOf((char) hex[j]));
|
||||||
hexOut.append( Character.valueOf( (char)hex[j+1]));
|
hexOut.append(Character.valueOf((char) hex[j + 1]));
|
||||||
if ((j+2) % 4 == 0) hexOut.append( ' ');
|
if ((j + 2) % 4 == 0) hexOut.append(' ');
|
||||||
|
|
||||||
int dataChar = data[j/2];
|
int dataChar = data[j / 2];
|
||||||
if (dataChar >= 32 && dataChar < 127) chrOut.append( Character.valueOf( (char)dataChar));
|
if (dataChar >= 32 && dataChar < 127) chrOut.append(Character.valueOf((char) dataChar));
|
||||||
else chrOut.append( '.');
|
else chrOut.append('.');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hexDumpOut.append( hexOut.toString());
|
hexDumpOut.append(hexOut.toString());
|
||||||
for (int k = hexOut.length(); k < 50; k++) hexDumpOut.append(' ');
|
for (int k = hexOut.length(); k < 50; k++) hexDumpOut.append(' ');
|
||||||
hexDumpOut.append( " ");
|
hexDumpOut.append(" ");
|
||||||
hexDumpOut.append( chrOut);
|
hexDumpOut.append(chrOut);
|
||||||
hexDumpOut.append("\n");
|
hexDumpOut.append("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
return hexDumpOut.toString();
|
return hexDumpOut.toString();
|
||||||
}
|
} catch (IOException x) {
|
||||||
catch (IOException x) {
|
throw new IllegalStateException(x.getClass().getName() + ": " + x.getMessage());
|
||||||
throw new IllegalStateException( x.getClass().getName() + ": " + x.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
package kellinwood.security.zipsigner;
|
package kellinwood.security.zipsigner;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -32,12 +33,11 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
public class HexEncoder
|
public class HexEncoder {
|
||||||
{
|
|
||||||
protected final byte[] encodingTable =
|
protected final byte[] encodingTable =
|
||||||
{
|
{
|
||||||
(byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7',
|
(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) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f'
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -45,11 +45,9 @@ public class HexEncoder
|
|||||||
*/
|
*/
|
||||||
protected final byte[] decodingTable = new byte[128];
|
protected final byte[] decodingTable = new byte[128];
|
||||||
|
|
||||||
protected void initialiseDecodingTable()
|
protected void initialiseDecodingTable() {
|
||||||
{
|
for (int i = 0; i < encodingTable.length; i++) {
|
||||||
for (int i = 0; i < encodingTable.length; i++)
|
decodingTable[encodingTable[i]] = (byte) i;
|
||||||
{
|
|
||||||
decodingTable[encodingTable[i]] = (byte)i;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
decodingTable['A'] = decodingTable['a'];
|
decodingTable['A'] = decodingTable['a'];
|
||||||
@ -60,8 +58,7 @@ public class HexEncoder
|
|||||||
decodingTable['F'] = decodingTable['f'];
|
decodingTable['F'] = decodingTable['f'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public HexEncoder()
|
public HexEncoder() {
|
||||||
{
|
|
||||||
initialiseDecodingTable();
|
initialiseDecodingTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,10 +72,8 @@ public class HexEncoder
|
|||||||
int off,
|
int off,
|
||||||
int length,
|
int length,
|
||||||
OutputStream out)
|
OutputStream out)
|
||||||
throws IOException
|
throws IOException {
|
||||||
{
|
for (int i = off; i < (off + length); i++) {
|
||||||
for (int i = off; i < (off + length); i++)
|
|
||||||
{
|
|
||||||
int v = data[i] & 0xff;
|
int v = data[i] & 0xff;
|
||||||
|
|
||||||
out.write(encodingTable[(v >>> 4)]);
|
out.write(encodingTable[(v >>> 4)]);
|
||||||
@ -89,9 +84,8 @@ public class HexEncoder
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean ignore(
|
private boolean ignore(
|
||||||
char c)
|
char c) {
|
||||||
{
|
return (c == '\n' || c == '\r' || c == '\t' || c == ' ');
|
||||||
return (c == '\n' || c =='\r' || c == '\t' || c == ' ');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -105,17 +99,14 @@ public class HexEncoder
|
|||||||
int off,
|
int off,
|
||||||
int length,
|
int length,
|
||||||
OutputStream out)
|
OutputStream out)
|
||||||
throws IOException
|
throws IOException {
|
||||||
{
|
|
||||||
byte b1, b2;
|
byte b1, b2;
|
||||||
int outLen = 0;
|
int outLen = 0;
|
||||||
|
|
||||||
int end = off + length;
|
int end = off + length;
|
||||||
|
|
||||||
while (end > off)
|
while (end > off) {
|
||||||
{
|
if (!ignore((char) data[end - 1])) {
|
||||||
if (!ignore((char)data[end - 1]))
|
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,17 +114,14 @@ public class HexEncoder
|
|||||||
}
|
}
|
||||||
|
|
||||||
int i = off;
|
int i = off;
|
||||||
while (i < end)
|
while (i < end) {
|
||||||
{
|
while (i < end && ignore((char) data[i])) {
|
||||||
while (i < end && ignore((char)data[i]))
|
|
||||||
{
|
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
b1 = decodingTable[data[i++]];
|
b1 = decodingTable[data[i++]];
|
||||||
|
|
||||||
while (i < end && ignore((char)data[i]))
|
while (i < end && ignore((char) data[i])) {
|
||||||
{
|
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,17 +144,14 @@ public class HexEncoder
|
|||||||
public int decode(
|
public int decode(
|
||||||
String data,
|
String data,
|
||||||
OutputStream out)
|
OutputStream out)
|
||||||
throws IOException
|
throws IOException {
|
||||||
{
|
|
||||||
byte b1, b2;
|
byte b1, b2;
|
||||||
int length = 0;
|
int length = 0;
|
||||||
|
|
||||||
int end = data.length();
|
int end = data.length();
|
||||||
|
|
||||||
while (end > 0)
|
while (end > 0) {
|
||||||
{
|
if (!ignore(data.charAt(end - 1))) {
|
||||||
if (!ignore(data.charAt(end - 1)))
|
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,17 +159,14 @@ public class HexEncoder
|
|||||||
}
|
}
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (i < end)
|
while (i < end) {
|
||||||
{
|
while (i < end && ignore(data.charAt(i))) {
|
||||||
while (i < end && ignore(data.charAt(i)))
|
|
||||||
{
|
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
b1 = decodingTable[data.charAt(i++)];
|
b1 = decodingTable[data.charAt(i++)];
|
||||||
|
|
||||||
while (i < end && ignore(data.charAt(i)))
|
while (i < end && ignore(data.charAt(i))) {
|
||||||
{
|
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2010 Ken Ellinwood
|
* Copyright (C) 2010 Ken Ellinwood
|
||||||
*
|
*
|
||||||
@ -15,6 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kellinwood.security.zipsigner;
|
package kellinwood.security.zipsigner;
|
||||||
|
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
@ -38,16 +37,14 @@ public class KeySet {
|
|||||||
public 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.name = name;
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
this.privateKey = privateKey;
|
this.privateKey = privateKey;
|
||||||
this.sigBlockTemplate = sigBlockTemplate;
|
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.name = name;
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
this.privateKey = privateKey;
|
this.privateKey = privateKey;
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kellinwood.security.zipsigner;
|
package kellinwood.security.zipsigner;
|
||||||
|
|
||||||
public class ProgressEvent {
|
public class ProgressEvent {
|
||||||
@ -27,18 +28,23 @@ public class ProgressEvent {
|
|||||||
public String getMessage() {
|
public String getMessage() {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMessage(String message) {
|
public void setMessage(String message) {
|
||||||
this.message = message;
|
this.message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getPercentDone() {
|
public int getPercentDone() {
|
||||||
return percentDone;
|
return percentDone;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPercentDone(int percentDone) {
|
public void setPercentDone(int percentDone) {
|
||||||
this.percentDone = percentDone;
|
this.percentDone = percentDone;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getPriority() {
|
public int getPriority() {
|
||||||
return priority;
|
return priority;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPriority(int priority) {
|
public void setPriority(int priority) {
|
||||||
this.priority = priority;
|
this.priority = priority;
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kellinwood.security.zipsigner;
|
package kellinwood.security.zipsigner;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -23,8 +24,7 @@ public class ProgressHelper {
|
|||||||
private int progressCurrentItem = 0;
|
private int progressCurrentItem = 0;
|
||||||
private ProgressEvent progressEvent = new ProgressEvent();
|
private ProgressEvent progressEvent = new ProgressEvent();
|
||||||
|
|
||||||
public void initProgress()
|
public void initProgress() {
|
||||||
{
|
|
||||||
progressTotalItems = 10000;
|
progressTotalItems = 10000;
|
||||||
progressCurrentItem = 0;
|
progressCurrentItem = 0;
|
||||||
}
|
}
|
||||||
@ -45,7 +45,7 @@ public class ProgressHelper {
|
|||||||
this.progressCurrentItem = progressCurrentItem;
|
this.progressCurrentItem = progressCurrentItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void progress( int priority, String message) {
|
public void progress(int priority, String message) {
|
||||||
|
|
||||||
progressCurrentItem += 1;
|
progressCurrentItem += 1;
|
||||||
|
|
||||||
@ -58,24 +58,22 @@ public class ProgressHelper {
|
|||||||
progressEvent.setMessage(message);
|
progressEvent.setMessage(message);
|
||||||
progressEvent.setPercentDone(percentDone);
|
progressEvent.setPercentDone(percentDone);
|
||||||
progressEvent.setPriority(priority);
|
progressEvent.setPriority(priority);
|
||||||
listener.onProgress( progressEvent);
|
listener.onProgress(progressEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArrayList<ProgressListener> listeners = new ArrayList<ProgressListener>();
|
private ArrayList<ProgressListener> listeners = new ArrayList<ProgressListener>();
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public synchronized void addProgressListener( ProgressListener l)
|
public synchronized void addProgressListener(ProgressListener l) {
|
||||||
{
|
ArrayList<ProgressListener> list = (ArrayList<ProgressListener>) listeners.clone();
|
||||||
ArrayList<ProgressListener> list = (ArrayList<ProgressListener>)listeners.clone();
|
|
||||||
list.add(l);
|
list.add(l);
|
||||||
listeners = list;
|
listeners = list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public synchronized void removeProgressListener( ProgressListener l)
|
public synchronized void removeProgressListener(ProgressListener l) {
|
||||||
{
|
ArrayList<ProgressListener> list = (ArrayList<ProgressListener>) listeners.clone();
|
||||||
ArrayList<ProgressListener> list = (ArrayList<ProgressListener>)listeners.clone();
|
|
||||||
list.remove(l);
|
list.remove(l);
|
||||||
listeners = list;
|
listeners = list;
|
||||||
}
|
}
|
||||||
|
@ -13,12 +13,14 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kellinwood.security.zipsigner;
|
package kellinwood.security.zipsigner;
|
||||||
|
|
||||||
public interface ProgressListener {
|
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);
|
||||||
}
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
package kellinwood.security.zipsigner;
|
package kellinwood.security.zipsigner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,7 +15,9 @@ public interface ResourceAdapter {
|
|||||||
GENERATING_SIGNATURE_FILE,
|
GENERATING_SIGNATURE_FILE,
|
||||||
GENERATING_SIGNATURE_BLOCK,
|
GENERATING_SIGNATURE_BLOCK,
|
||||||
COPYING_ZIP_ENTRY
|
COPYING_ZIP_ENTRY
|
||||||
};
|
}
|
||||||
|
|
||||||
public String getString( Item item, Object... args);
|
;
|
||||||
|
|
||||||
|
public String getString(Item item, Object... args);
|
||||||
}
|
}
|
||||||
|
@ -13,59 +13,56 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kellinwood.security.zipsigner;
|
package kellinwood.security.zipsigner;
|
||||||
|
|
||||||
|
import javax.crypto.BadPaddingException;
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
|
||||||
|
|
||||||
@SuppressWarnings("restriction")
|
@SuppressWarnings("restriction")
|
||||||
public class ZipSignature {
|
public class ZipSignature {
|
||||||
|
|
||||||
byte[] beforeAlgorithmIdBytes = { 0x30, 0x21 };
|
byte[] beforeAlgorithmIdBytes = {0x30, 0x21};
|
||||||
|
|
||||||
// byte[] algorithmIdBytes;
|
// byte[] algorithmIdBytes;
|
||||||
// algorithmIdBytes = sun.security.x509.AlgorithmId.get("SHA1").encode();
|
// 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;
|
Cipher cipher;
|
||||||
|
|
||||||
MessageDigest md;
|
MessageDigest md;
|
||||||
|
|
||||||
|
|
||||||
public ZipSignature() throws IOException, GeneralSecurityException
|
public ZipSignature() throws IOException, GeneralSecurityException {
|
||||||
{
|
|
||||||
md = MessageDigest.getInstance("SHA1");
|
md = MessageDigest.getInstance("SHA1");
|
||||||
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
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);
|
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update( byte[] data) {
|
public void update(byte[] data) {
|
||||||
md.update( data);
|
md.update(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update( byte[] data, int offset, int count) {
|
public void update(byte[] data, int offset, int count) {
|
||||||
md.update( data, offset, count);
|
md.update(data, offset, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] sign() throws BadPaddingException, IllegalBlockSizeException
|
public byte[] sign() throws BadPaddingException, IllegalBlockSizeException {
|
||||||
{
|
cipher.update(beforeAlgorithmIdBytes);
|
||||||
cipher.update( beforeAlgorithmIdBytes);
|
cipher.update(algorithmIdBytes);
|
||||||
cipher.update( algorithmIdBytes);
|
cipher.update(afterAlgorithmIdBytes);
|
||||||
cipher.update( afterAlgorithmIdBytes);
|
cipher.update(md.digest());
|
||||||
cipher.update( md.digest());
|
|
||||||
return cipher.doFinal();
|
return cipher.doFinal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
* using signature block template files.
|
* using signature block template files.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package kellinwood.security.zipsigner;
|
package kellinwood.security.zipsigner;
|
||||||
|
|
||||||
import kellinwood.logging.LoggerInterface;
|
import kellinwood.logging.LoggerInterface;
|
||||||
@ -38,17 +39,40 @@ import javax.crypto.Cipher;
|
|||||||
import javax.crypto.EncryptedPrivateKeyInfo;
|
import javax.crypto.EncryptedPrivateKeyInfo;
|
||||||
import javax.crypto.SecretKeyFactory;
|
import javax.crypto.SecretKeyFactory;
|
||||||
import javax.crypto.spec.PBEKeySpec;
|
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.lang.reflect.Method;
|
||||||
import java.net.URL;
|
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.Certificate;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
import java.security.spec.KeySpec;
|
import java.security.spec.KeySpec;
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
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.Attributes;
|
||||||
import java.util.jar.JarFile;
|
import java.util.jar.JarFile;
|
||||||
import java.util.jar.Manifest;
|
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
|
* 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
|
* API to sign JAR files (including APKs and Zip/OTA updates) in
|
||||||
* a way compatible with the mincrypt verifier, using SHA1 and RSA keys.
|
* 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.
|
* 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;
|
private boolean canceled = false;
|
||||||
|
|
||||||
@ -78,11 +101,11 @@ public class ZipSigner
|
|||||||
private static Pattern stripPattern =
|
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;
|
KeySet keySet = null;
|
||||||
|
|
||||||
public static LoggerInterface getLogger() {
|
public static LoggerInterface getLogger() {
|
||||||
if (log == null) log = LoggerManager.getLogger( ZipSigner.class.getName());
|
if (log == null) log = LoggerManager.getLogger(ZipSigner.class.getName());
|
||||||
return log;
|
return log;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,21 +117,20 @@ public class ZipSigner
|
|||||||
|
|
||||||
// Allowable key modes.
|
// Allowable key modes.
|
||||||
public static final String[] SUPPORTED_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
|
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();
|
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
|
// 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("aa9852bc5a53272ac8031d49b65e4b0e", "media");
|
||||||
autoKeyDetect.put( "e60418c4b638f20d0721e115674ca11f", "platform");
|
autoKeyDetect.put("e60418c4b638f20d0721e115674ca11f", "platform");
|
||||||
autoKeyDetect.put( "3e24e49741b60c215c010dc6048fca7d", "shared");
|
autoKeyDetect.put("3e24e49741b60c215c010dc6048fca7d", "shared");
|
||||||
autoKeyDetect.put( "dab2cead827ef5313f28e22b6fa8479f", "testkey");
|
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
|
// 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);
|
autoKeyObservable.addObserver(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,16 +151,14 @@ public class ZipSigner
|
|||||||
return keymode;
|
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);
|
if (getLogger().isDebugEnabled()) getLogger().debug("setKeymode: " + km);
|
||||||
keymode = km;
|
keymode = km;
|
||||||
if (keymode.startsWith(MODE_AUTO)) {
|
if (keymode.startsWith(MODE_AUTO)) {
|
||||||
keySet = null;
|
keySet = null;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
progressHelper.initProgress();
|
progressHelper.initProgress();
|
||||||
loadKeys( keymode);
|
loadKeys(keymode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,9 +167,8 @@ public class ZipSigner
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected String autoDetectKey( String mode, Map<String,ZioEntry> zioEntries)
|
protected String autoDetectKey(String mode, Map<String, ZioEntry> zioEntries)
|
||||||
throws NoSuchAlgorithmException, IOException
|
throws NoSuchAlgorithmException, IOException {
|
||||||
{
|
|
||||||
boolean debug = getLogger().isDebugEnabled();
|
boolean debug = getLogger().isDebugEnabled();
|
||||||
|
|
||||||
if (!mode.startsWith(MODE_AUTO)) return mode;
|
if (!mode.startsWith(MODE_AUTO)) return mode;
|
||||||
@ -158,7 +177,7 @@ public class ZipSigner
|
|||||||
// Auto-determine which keys to use
|
// Auto-determine which keys to use
|
||||||
String keyName = null;
|
String keyName = null;
|
||||||
// Start by finding the signature block file in the input.
|
// 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();
|
String entryName = entry.getKey();
|
||||||
if (entryName.startsWith("META-INF/") && entryName.endsWith(".RSA")) {
|
if (entryName.startsWith("META-INF/") && entryName.endsWith(".RSA")) {
|
||||||
|
|
||||||
@ -167,18 +186,18 @@ public class ZipSigner
|
|||||||
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
||||||
byte[] entryData = entry.getValue().getData();
|
byte[] entryData = entry.getValue().getData();
|
||||||
if (entryData.length < 1458) break; // sig block too short to be a supported key
|
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();
|
byte[] rawDigest = md5.digest();
|
||||||
|
|
||||||
// Create the hex representation of the digest value
|
// Create the hex representation of the digest value
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
for( byte b : rawDigest) {
|
for (byte b : rawDigest) {
|
||||||
builder.append( String.format("%02x", b));
|
builder.append(String.format("%02x", b));
|
||||||
}
|
}
|
||||||
|
|
||||||
String md5String = builder.toString();
|
String md5String = builder.toString();
|
||||||
// Lookup the key name
|
// Lookup the key name
|
||||||
keyName = autoKeyDetect.get( md5String);
|
keyName = autoKeyDetect.get(md5String);
|
||||||
|
|
||||||
|
|
||||||
if (debug) {
|
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
|
// 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;
|
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.
|
// 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);
|
if (debug) getLogger().debug("Unable to determine key, returning: " + KEY_NONE);
|
||||||
return KEY_NONE;
|
return KEY_NONE;
|
||||||
@ -212,44 +230,41 @@ public class ZipSigner
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Loads one of the built-in keys (media, platform, shared, testkey)
|
// Loads one of the built-in keys (media, platform, shared, testkey)
|
||||||
public void loadKeys( String name)
|
public void loadKeys(String name)
|
||||||
throws IOException, GeneralSecurityException
|
throws IOException, GeneralSecurityException {
|
||||||
{
|
|
||||||
|
|
||||||
keySet = loadedKeys.get(name);
|
keySet = loadedKeys.get(name);
|
||||||
if (keySet != null) return;
|
if (keySet != null) return;
|
||||||
|
|
||||||
keySet = new KeySet();
|
keySet = new KeySet();
|
||||||
keySet.setName(name);
|
keySet.setName(name);
|
||||||
loadedKeys.put( name, keySet);
|
loadedKeys.put(name, keySet);
|
||||||
|
|
||||||
if (KEY_NONE.equals(name)) return;
|
if (KEY_NONE.equals(name)) return;
|
||||||
|
|
||||||
issueLoadingCertAndKeysProgressEvent();
|
issueLoadingCertAndKeysProgressEvent();
|
||||||
|
|
||||||
// load the private key
|
// load the private key
|
||||||
URL privateKeyUrl = getClass().getResource("/keys/"+name+".pk8");
|
URL privateKeyUrl = getClass().getResource("/keys/" + name + ".pk8");
|
||||||
keySet.setPrivateKey(readPrivateKey(privateKeyUrl, null));
|
keySet.setPrivateKey(readPrivateKey(privateKeyUrl, null));
|
||||||
|
|
||||||
// load the certificate
|
// load the certificate
|
||||||
URL publicKeyUrl = getClass().getResource("/keys/"+name+".x509.pem");
|
URL publicKeyUrl = getClass().getResource("/keys/" + name + ".x509.pem");
|
||||||
keySet.setPublicKey(readPublicKey(publicKeyUrl));
|
keySet.setPublicKey(readPublicKey(publicKeyUrl));
|
||||||
|
|
||||||
// load the signature block template
|
// load the signature block template
|
||||||
URL sigBlockTemplateUrl = getClass().getResource("/keys/"+name+".sbt");
|
URL sigBlockTemplateUrl = getClass().getResource("/keys/" + name + ".sbt");
|
||||||
if (sigBlockTemplateUrl != null) {
|
if (sigBlockTemplateUrl != null) {
|
||||||
keySet.setSigBlockTemplate(readContentAsBytes(sigBlockTemplateUrl));
|
keySet.setSigBlockTemplate(readContentAsBytes(sigBlockTemplateUrl));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setKeys( String name, X509Certificate publicKey, PrivateKey privateKey, byte[] signatureBlockTemplate)
|
public void setKeys(String name, X509Certificate publicKey, PrivateKey privateKey, byte[] signatureBlockTemplate) {
|
||||||
{
|
keySet = new KeySet(name, publicKey, privateKey, signatureBlockTemplate);
|
||||||
keySet = new KeySet( name, publicKey, privateKey, signatureBlockTemplate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setKeys( String name, X509Certificate publicKey, PrivateKey privateKey, String signatureAlgorithm, byte[] signatureBlockTemplate)
|
public void setKeys(String name, X509Certificate publicKey, PrivateKey privateKey, String signatureAlgorithm, byte[] signatureBlockTemplate) {
|
||||||
{
|
keySet = new KeySet(name, publicKey, privateKey, signatureAlgorithm, signatureBlockTemplate);
|
||||||
keySet = new KeySet( name, publicKey, privateKey, signatureAlgorithm, signatureBlockTemplate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeySet getKeySet() {
|
public KeySet getKeySet() {
|
||||||
@ -271,11 +286,10 @@ public class ZipSigner
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void loadProvider( String providerClassName)
|
public void loadProvider(String providerClassName)
|
||||||
throws ClassNotFoundException, IllegalAccessException, InstantiationException
|
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||||
{
|
|
||||||
Class providerClass = Class.forName(providerClassName);
|
Class providerClass = Class.forName(providerClassName);
|
||||||
Provider provider = (Provider)providerClass.newInstance();
|
Provider provider = (Provider) providerClass.newInstance();
|
||||||
Security.insertProviderAt(provider, 1);
|
Security.insertProviderAt(provider, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,7 +307,7 @@ public class ZipSigner
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt an encrypted PKCS 8 format private key.
|
* Decrypt an encrypted PKCS 8 format private key.
|
||||||
*
|
* <p>
|
||||||
* Based on ghstark's post on Aug 6, 2006 at
|
* Based on ghstark's post on Aug 6, 2006 at
|
||||||
* http://forums.sun.com/thread.jspa?threadID=758133&messageID=4330949
|
* http://forums.sun.com/thread.jspa?threadID=758133&messageID=4330949
|
||||||
*
|
*
|
||||||
@ -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
|
* Fetch the content at the specified URL and return it as a byte array.
|
||||||
{
|
*/
|
||||||
return readContentAsBytes( contentUrl.openStream());
|
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();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
|
||||||
byte[] buffer = new byte[2048];
|
byte[] buffer = new byte[2048];
|
||||||
|
|
||||||
int numRead = input.read( buffer);
|
int numRead = input.read(buffer);
|
||||||
while (numRead != -1) {
|
while (numRead != -1) {
|
||||||
baos.write( buffer, 0, numRead);
|
baos.write(buffer, 0, numRead);
|
||||||
numRead = input.read( buffer);
|
numRead = input.read(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] bytes = baos.toByteArray();
|
byte[] bytes = baos.toByteArray();
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Read a PKCS 8 format private key. */
|
/**
|
||||||
|
* Read a PKCS 8 format private key.
|
||||||
|
*/
|
||||||
public PrivateKey readPrivateKey(URL privateKeyUrl, String keyPassword)
|
public PrivateKey readPrivateKey(URL privateKeyUrl, String keyPassword)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
DataInputStream input = new DataInputStream( privateKeyUrl.openStream());
|
DataInputStream input = new DataInputStream(privateKeyUrl.openStream());
|
||||||
try {
|
try {
|
||||||
byte[] bytes = readContentAsBytes( input);
|
byte[] bytes = readContentAsBytes(input);
|
||||||
|
|
||||||
KeySpec spec = decryptPrivateKey(bytes, keyPassword);
|
KeySpec spec = decryptPrivateKey(bytes, keyPassword);
|
||||||
if (spec == null) {
|
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)
|
* Add the SHA1 of every file to the manifest, creating it if necessary.
|
||||||
throws IOException, GeneralSecurityException
|
*/
|
||||||
{
|
private Manifest addDigestsToManifest(Map<String, ZioEntry> entries)
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
Manifest input = null;
|
Manifest input = null;
|
||||||
ZioEntry manifestEntry = entries.get(JarFile.MANIFEST_NAME);
|
ZioEntry manifestEntry = entries.get(JarFile.MANIFEST_NAME);
|
||||||
if (manifestEntry != null) {
|
if (manifestEntry != null) {
|
||||||
input = new Manifest();
|
input = new Manifest();
|
||||||
input.read( manifestEntry.getInputStream());
|
input.read(manifestEntry.getInputStream());
|
||||||
}
|
}
|
||||||
Manifest output = new Manifest();
|
Manifest output = new Manifest();
|
||||||
Attributes main = output.getMainAttributes();
|
Attributes main = output.getMainAttributes();
|
||||||
@ -400,21 +419,20 @@ public class ZipSigner
|
|||||||
// map will be deterministic.
|
// map will be deterministic.
|
||||||
|
|
||||||
TreeMap<String, ZioEntry> byName = new TreeMap<String, ZioEntry>();
|
TreeMap<String, ZioEntry> byName = new TreeMap<String, ZioEntry>();
|
||||||
byName.putAll( entries);
|
byName.putAll(entries);
|
||||||
|
|
||||||
boolean debug = getLogger().isDebugEnabled();
|
boolean debug = getLogger().isDebugEnabled();
|
||||||
if (debug) getLogger().debug("Manifest entries:");
|
if (debug) getLogger().debug("Manifest entries:");
|
||||||
for (ZioEntry entry: byName.values()) {
|
for (ZioEntry entry : byName.values()) {
|
||||||
if (canceled) break;
|
if (canceled) break;
|
||||||
String name = entry.getName();
|
String name = entry.getName();
|
||||||
if (debug) getLogger().debug(name);
|
if (debug) getLogger().debug(name);
|
||||||
if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) &&
|
if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) &&
|
||||||
!name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) &&
|
!name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) &&
|
||||||
(stripPattern == null ||
|
(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();
|
InputStream data = entry.getInputStream();
|
||||||
while ((num = data.read(buffer)) > 0) {
|
while ((num = data.read(buffer)) > 0) {
|
||||||
md.update(buffer, 0, num);
|
md.update(buffer, 0, num);
|
||||||
@ -423,7 +441,7 @@ public class ZipSigner
|
|||||||
Attributes attr = null;
|
Attributes attr = null;
|
||||||
if (input != null) {
|
if (input != null) {
|
||||||
java.util.jar.Attributes inAttr = input.getAttributes(name);
|
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();
|
if (attr == null) attr = new Attributes();
|
||||||
attr.putValue("SHA1-Digest", Base64.encode(md.digest()));
|
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)
|
private void generateSignatureFile(Manifest manifest, OutputStream out)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
out.write( ("Signature-Version: 1.0\r\n").getBytes());
|
out.write(("Signature-Version: 1.0\r\n").getBytes());
|
||||||
out.write( ("Created-By: 1.0 (Android SignApk)\r\n").getBytes());
|
out.write(("Created-By: 1.0 (Android SignApk)\r\n").getBytes());
|
||||||
|
|
||||||
|
|
||||||
// BASE64Encoder base64 = new BASE64Encoder();
|
// BASE64Encoder base64 = new BASE64Encoder();
|
||||||
@ -452,32 +472,33 @@ public class ZipSigner
|
|||||||
manifest.write(print);
|
manifest.write(print);
|
||||||
print.flush();
|
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();
|
Map<String, Attributes> entries = manifest.getEntries();
|
||||||
for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
|
for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
|
||||||
if (canceled) break;
|
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.
|
// Digest of the manifest stanza for this entry.
|
||||||
String nameEntry = "Name: " + entry.getKey() + "\r\n";
|
String nameEntry = "Name: " + entry.getKey() + "\r\n";
|
||||||
print.print( nameEntry);
|
print.print(nameEntry);
|
||||||
for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {
|
for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {
|
||||||
print.print(att.getKey() + ": " + att.getValue() + "\r\n");
|
print.print(att.getKey() + ": " + att.getValue() + "\r\n");
|
||||||
}
|
}
|
||||||
print.print("\r\n");
|
print.print("\r\n");
|
||||||
print.flush();
|
print.flush();
|
||||||
|
|
||||||
out.write( nameEntry.getBytes());
|
out.write(nameEntry.getBytes());
|
||||||
out.write( ("SHA1-Digest: " + Base64.encode(md.digest()) + "\r\n\r\n").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")
|
@SuppressWarnings("unchecked")
|
||||||
private void writeSignatureBlock( KeySet keySet, byte[] signatureFileBytes, OutputStream out)
|
private void writeSignatureBlock(KeySet keySet, byte[] signatureFileBytes, OutputStream out)
|
||||||
throws IOException, GeneralSecurityException
|
throws IOException, GeneralSecurityException {
|
||||||
{
|
|
||||||
if (keySet.getSigBlockTemplate() != null) {
|
if (keySet.getSigBlockTemplate() != null) {
|
||||||
|
|
||||||
// Can't use default Signature on Android. Although it generates a signature that can be verified by jarsigner,
|
// 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);
|
signature.update(signatureFileBytes);
|
||||||
byte[] signatureBytes = signature.sign();
|
byte[] signatureBytes = signature.sign();
|
||||||
|
|
||||||
out.write( keySet.getSigBlockTemplate());
|
out.write(keySet.getSigBlockTemplate());
|
||||||
out.write( signatureBytes);
|
out.write(signatureBytes);
|
||||||
|
|
||||||
if (getLogger().isDebugEnabled()) {
|
if (getLogger().isDebugEnabled()) {
|
||||||
|
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA1");
|
MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||||
md.update( signatureFileBytes);
|
md.update(signatureFileBytes);
|
||||||
byte[] sfDigest = md.digest();
|
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 cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
||||||
cipher.init(Cipher.DECRYPT_MODE, keySet.getPublicKey());
|
cipher.init(Cipher.DECRYPT_MODE, keySet.getPublicKey());
|
||||||
|
|
||||||
byte[] tmpData = cipher.doFinal( signatureBytes);
|
byte[] tmpData = cipher.doFinal(signatureBytes);
|
||||||
getLogger().debug( "Signature Decrypted: \n" + HexDumpEncoder.encode(tmpData));
|
getLogger().debug("Signature Decrypted: \n" + HexDumpEncoder.encode(tmpData));
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
try {
|
try {
|
||||||
byte[] sigBlock = null;
|
byte[] sigBlock = null;
|
||||||
// Use reflection to call the optional generator.
|
// Use reflection to call the optional generator.
|
||||||
Class generatorClass = Class.forName("kellinwood.security.zipsigner.optional.SignatureBlockGenerator");
|
Class generatorClass = Class.forName("kellinwood.security.zipsigner.optional.SignatureBlockGenerator");
|
||||||
Method generatorMethod = generatorClass.getMethod("generate", KeySet.class, (new byte[1]).getClass());
|
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);
|
out.write(sigBlock);
|
||||||
} catch (Exception x) {
|
} 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
|
* reduce variation in the output file and make incremental OTAs
|
||||||
* more efficient.
|
* more efficient.
|
||||||
*/
|
*/
|
||||||
private void copyFiles(Manifest manifest, Map<String,ZioEntry> input, ZipOutput output, long timestamp)
|
private void copyFiles(Manifest manifest, Map<String, ZioEntry> input, ZipOutput output, long timestamp)
|
||||||
throws IOException
|
throws IOException {
|
||||||
{
|
|
||||||
Map<String, Attributes> entries = manifest.getEntries();
|
Map<String, Attributes> entries = manifest.getEntries();
|
||||||
List<String> names = new ArrayList<String>(entries.keySet());
|
List<String> names = new ArrayList<String>(entries.keySet());
|
||||||
Collections.sort(names);
|
Collections.sort(names);
|
||||||
@ -548,13 +567,12 @@ public class ZipSigner
|
|||||||
/**
|
/**
|
||||||
* Copy all the files from input to output.
|
* Copy all the files from input to output.
|
||||||
*/
|
*/
|
||||||
private void copyFiles(Map<String,ZioEntry> input, ZipOutput output)
|
private void copyFiles(Map<String, ZioEntry> input, ZipOutput output)
|
||||||
throws IOException
|
throws IOException {
|
||||||
{
|
|
||||||
int i = 1;
|
int i = 1;
|
||||||
for (ZioEntry inEntry : input.values()) {
|
for (ZioEntry inEntry : input.values()) {
|
||||||
if (canceled) break;
|
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;
|
i += 1;
|
||||||
output.write(inEntry);
|
output.write(inEntry);
|
||||||
}
|
}
|
||||||
@ -563,7 +581,7 @@ public class ZipSigner
|
|||||||
/**
|
/**
|
||||||
* @deprecated - use the version that takes the passwords as char[]
|
* @deprecated - use the version that takes the passwords as char[]
|
||||||
*/
|
*/
|
||||||
public void signZip( URL keystoreURL,
|
public void signZip(URL keystoreURL,
|
||||||
String keystoreType,
|
String keystoreType,
|
||||||
String keystorePw,
|
String keystorePw,
|
||||||
String certAlias,
|
String certAlias,
|
||||||
@ -571,12 +589,11 @@ public class ZipSigner
|
|||||||
String inputZipFilename,
|
String inputZipFilename,
|
||||||
String outputZipFilename)
|
String outputZipFilename)
|
||||||
throws ClassNotFoundException, IllegalAccessException, InstantiationException,
|
throws ClassNotFoundException, IllegalAccessException, InstantiationException,
|
||||||
IOException, GeneralSecurityException
|
IOException, GeneralSecurityException {
|
||||||
{
|
signZip(keystoreURL, keystoreType, keystorePw.toCharArray(), certAlias, certPw.toCharArray(), "SHA1withRSA", inputZipFilename, outputZipFilename);
|
||||||
signZip( keystoreURL, keystoreType, keystorePw.toCharArray(), certAlias, certPw.toCharArray(), "SHA1withRSA", inputZipFilename, outputZipFilename);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void signZip( URL keystoreURL,
|
public void signZip(URL keystoreURL,
|
||||||
String keystoreType,
|
String keystoreType,
|
||||||
char[] keystorePw,
|
char[] keystorePw,
|
||||||
String certAlias,
|
String certAlias,
|
||||||
@ -585,8 +602,7 @@ public class ZipSigner
|
|||||||
String inputZipFilename,
|
String inputZipFilename,
|
||||||
String outputZipFilename)
|
String outputZipFilename)
|
||||||
throws ClassNotFoundException, IllegalAccessException, InstantiationException,
|
throws ClassNotFoundException, IllegalAccessException, InstantiationException,
|
||||||
IOException, GeneralSecurityException
|
IOException, GeneralSecurityException {
|
||||||
{
|
|
||||||
InputStream keystoreStream = null;
|
InputStream keystoreStream = null;
|
||||||
|
|
||||||
|
|
||||||
@ -598,72 +614,68 @@ public class ZipSigner
|
|||||||
keystoreStream = keystoreURL.openStream();
|
keystoreStream = keystoreURL.openStream();
|
||||||
keystore.load(keystoreStream, keystorePw);
|
keystore.load(keystoreStream, keystorePw);
|
||||||
Certificate cert = keystore.getCertificate(certAlias);
|
Certificate cert = keystore.getCertificate(certAlias);
|
||||||
X509Certificate publicKey = (X509Certificate)cert;
|
X509Certificate publicKey = (X509Certificate) cert;
|
||||||
Key key = keystore.getKey(certAlias, certPw);
|
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);
|
signZip(inputZipFilename, outputZipFilename);
|
||||||
}
|
} finally {
|
||||||
finally {
|
|
||||||
if (keystoreStream != null) keystoreStream.close();
|
if (keystoreStream != null) keystoreStream.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign the input with the default test key and certificate.
|
||||||
/** Sign the input with the default test key and certificate.
|
|
||||||
* Save result to output file.
|
* Save result to output file.
|
||||||
*/
|
*/
|
||||||
public void signZip( Map<String,ZioEntry> zioEntries, String outputZipFilename)
|
public void signZip(Map<String, ZioEntry> zioEntries, String outputZipFilename)
|
||||||
throws IOException, GeneralSecurityException
|
throws IOException, GeneralSecurityException {
|
||||||
{
|
|
||||||
progressHelper.initProgress();
|
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,
|
/**
|
||||||
|
* Sign the file using the given public key cert, private key,
|
||||||
* and signature block template. The signature block template
|
* and signature block template. The signature block template
|
||||||
* parameter may be null, but if so
|
* parameter may be null, but if so
|
||||||
* android-sun-jarsign-support.jar must be in the classpath.
|
* android-sun-jarsign-support.jar must be in the classpath.
|
||||||
*/
|
*/
|
||||||
public void signZip( String inputZipFilename, String outputZipFilename)
|
public void signZip(String inputZipFilename, String outputZipFilename)
|
||||||
throws IOException, GeneralSecurityException
|
throws IOException, GeneralSecurityException {
|
||||||
{
|
File inFile = new File(inputZipFilename).getCanonicalFile();
|
||||||
File inFile = new File( inputZipFilename).getCanonicalFile();
|
File outFile = new File(outputZipFilename).getCanonicalFile();
|
||||||
File outFile = new File( outputZipFilename).getCanonicalFile();
|
|
||||||
|
|
||||||
if (inFile.equals(outFile)) {
|
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.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;
|
ZipInput input = null;
|
||||||
OutputStream outStream = null;
|
OutputStream outStream = null;
|
||||||
try {
|
try {
|
||||||
input = ZipInput.read( inputZipFilename);
|
input = ZipInput.read(inputZipFilename);
|
||||||
outStream = new FileOutputStream( outputZipFilename);
|
outStream = new FileOutputStream(outputZipFilename);
|
||||||
signZip(input.getEntries(), outStream, outputZipFilename);
|
signZip(input.getEntries(), outStream, outputZipFilename);
|
||||||
}
|
} finally {
|
||||||
finally {
|
if (input != null) input.close();
|
||||||
if(input != null) input.close();
|
if (outStream != null) outStream.close();
|
||||||
if(outStream != null) outStream.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sign the
|
/**
|
||||||
|
* Sign the
|
||||||
* and signature block template. The signature block template
|
* and signature block template. The signature block template
|
||||||
* parameter may be null, but if so
|
* parameter may be null, but if so
|
||||||
* android-sun-jarsign-support.jar must be in the classpath.
|
* android-sun-jarsign-support.jar must be in the classpath.
|
||||||
*/
|
*/
|
||||||
public void signZip( Map<String,ZioEntry> zioEntries, OutputStream outputStream, String outputZipFilename)
|
public void signZip(Map<String, ZioEntry> zioEntries, OutputStream outputStream, String outputZipFilename)
|
||||||
throws IOException, GeneralSecurityException
|
throws IOException, GeneralSecurityException {
|
||||||
{
|
|
||||||
boolean debug = getLogger().isDebugEnabled();
|
boolean debug = getLogger().isDebugEnabled();
|
||||||
|
|
||||||
progressHelper.initProgress();
|
progressHelper.initProgress();
|
||||||
@ -672,24 +684,23 @@ public class ZipSigner
|
|||||||
throw new IllegalStateException("No keys configured for signing the file!");
|
throw new IllegalStateException("No keys configured for signing the file!");
|
||||||
|
|
||||||
// Auto-determine which keys to use
|
// Auto-determine which keys to use
|
||||||
String keyName = this.autoDetectKey( keymode, zioEntries);
|
String keyName = this.autoDetectKey(keymode, zioEntries);
|
||||||
if (keyName == null)
|
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);
|
autoKeyObservable.notifyObservers(keyName);
|
||||||
|
|
||||||
loadKeys( keyName);
|
loadKeys(keyName);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ZipOutput zipOutput = null;
|
ZipOutput zipOutput = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
|
||||||
zipOutput = new ZipOutput( outputStream);
|
zipOutput = new ZipOutput(outputStream);
|
||||||
|
|
||||||
if (KEY_NONE.equals(keySet.getName())) {
|
if (KEY_NONE.equals(keySet.getName())) {
|
||||||
progressHelper.setProgressTotalItems(zioEntries.size());
|
progressHelper.setProgressTotalItems(zioEntries.size());
|
||||||
@ -700,13 +711,12 @@ public class ZipSigner
|
|||||||
|
|
||||||
// Calculate total steps to complete for accurate progress percentages.
|
// Calculate total steps to complete for accurate progress percentages.
|
||||||
int progressTotalItems = 0;
|
int progressTotalItems = 0;
|
||||||
for (ZioEntry entry: zioEntries.values()) {
|
for (ZioEntry entry : zioEntries.values()) {
|
||||||
String name = entry.getName();
|
String name = entry.getName();
|
||||||
if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) &&
|
if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) &&
|
||||||
!name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) &&
|
!name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) &&
|
||||||
(stripPattern == null ||
|
(stripPattern == null ||
|
||||||
!stripPattern.matcher(name).matches()))
|
!stripPattern.matcher(name).matches())) {
|
||||||
{
|
|
||||||
progressTotalItems += 3; // digest for manifest, digest in sig file, copy data
|
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);
|
// progress(ProgressEvent.PRORITY_NORMAL, JarFile.MANIFEST_NAME);
|
||||||
Manifest manifest = addDigestsToManifest(zioEntries);
|
Manifest manifest = addDigestsToManifest(zioEntries);
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
ZioEntry ze = new ZioEntry( JarFile.MANIFEST_NAME);
|
ZioEntry ze = new ZioEntry(JarFile.MANIFEST_NAME);
|
||||||
ze.setTime(timestamp);
|
ze.setTime(timestamp);
|
||||||
manifest.write(ze.getOutputStream());
|
manifest.write(ze.getOutputStream());
|
||||||
zipOutput.write(ze);
|
zipOutput.write(ze);
|
||||||
@ -736,51 +746,46 @@ public class ZipSigner
|
|||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
byte[] sfBytes = out.toByteArray();
|
byte[] sfBytes = out.toByteArray();
|
||||||
if (debug) {
|
if (debug) {
|
||||||
getLogger().debug( "Signature File: \n" + new String( sfBytes) + "\n" +
|
getLogger().debug("Signature File: \n" + new String(sfBytes) + "\n" +
|
||||||
HexDumpEncoder.encode( sfBytes));
|
HexDumpEncoder.encode(sfBytes));
|
||||||
}
|
}
|
||||||
ze.getOutputStream().write(sfBytes);
|
ze.getOutputStream().write(sfBytes);
|
||||||
zipOutput.write(ze);
|
zipOutput.write(ze);
|
||||||
|
|
||||||
// CERT.RSA
|
// 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 = new ZioEntry(CERT_RSA_NAME);
|
||||||
ze.setTime(timestamp);
|
ze.setTime(timestamp);
|
||||||
writeSignatureBlock(keySet, sfBytes, ze.getOutputStream());
|
writeSignatureBlock(keySet, sfBytes, ze.getOutputStream());
|
||||||
zipOutput.write( ze);
|
zipOutput.write(ze);
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
// Everything else
|
// Everything else
|
||||||
copyFiles(manifest, zioEntries, zipOutput, timestamp);
|
copyFiles(manifest, zioEntries, zipOutput, timestamp);
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
}
|
} finally {
|
||||||
finally {
|
|
||||||
if (zipOutput != null) zipOutput.close();
|
if (zipOutput != null) zipOutput.close();
|
||||||
if (canceled) {
|
if (canceled) {
|
||||||
try {
|
try {
|
||||||
if (outputZipFilename != null) new File( outputZipFilename).delete();
|
if (outputZipFilename != null) new File(outputZipFilename).delete();
|
||||||
}
|
} catch (Throwable t) {
|
||||||
catch (Throwable t) {
|
getLogger().warning(t.getClass().getName() + ":" + t.getMessage());
|
||||||
getLogger().warning( t.getClass().getName() + ":" + t.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addProgressListener( ProgressListener l)
|
public void addProgressListener(ProgressListener l) {
|
||||||
{
|
|
||||||
progressHelper.addProgressListener(l);
|
progressHelper.addProgressListener(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void removeProgressListener( ProgressListener l)
|
public synchronized void removeProgressListener(ProgressListener l) {
|
||||||
{
|
|
||||||
progressHelper.removeProgressListener(l);
|
progressHelper.removeProgressListener(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class AutoKeyObservable extends Observable
|
public static class AutoKeyObservable extends Observable {
|
||||||
{
|
|
||||||
@Override
|
@Override
|
||||||
public void notifyObservers(Object arg) {
|
public void notifyObservers(Object arg) {
|
||||||
super.setChanged();
|
super.setChanged();
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
package kellinwood.security.zipsigner.optional;
|
package kellinwood.security.zipsigner.optional;
|
||||||
|
|
||||||
import kellinwood.security.zipsigner.KeySet;
|
import kellinwood.security.zipsigner.KeySet;
|
||||||
@ -7,7 +8,10 @@ import org.bouncycastle.x509.X509V3CertificateGenerator;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
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.security.cert.X509Certificate;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
@ -17,7 +21,8 @@ import java.util.Date;
|
|||||||
*/
|
*/
|
||||||
public class CertCreator {
|
public class CertCreator {
|
||||||
|
|
||||||
/** Creates a new keystore and self-signed key. The key will have the same password as the key, and will be
|
/**
|
||||||
|
* 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
|
* RSA 2048, with the cert signed using SHA1withRSA. The certificate will have a validity of
|
||||||
* 30 years).
|
* 30 years).
|
||||||
*
|
*
|
||||||
@ -26,15 +31,14 @@ public class CertCreator {
|
|||||||
* @param keyName - the new key will have this as its alias within the keystore
|
* @param keyName - the new key will have this as its alias within the keystore
|
||||||
* @param distinguishedNameValues - contains Country, State, Locality,...,Common Name, etc.
|
* @param distinguishedNameValues - contains Country, State, Locality,...,Common Name, etc.
|
||||||
*/
|
*/
|
||||||
public static void createKeystoreAndKey( String storePath, char[] password,
|
public static void createKeystoreAndKey(String storePath, char[] password,
|
||||||
String keyName, DistinguishedNameValues distinguishedNameValues)
|
String keyName, DistinguishedNameValues distinguishedNameValues) {
|
||||||
{
|
|
||||||
createKeystoreAndKey(storePath, password, "RSA", 2048, keyName, password, "SHA1withRSA", 30,
|
createKeystoreAndKey(storePath, password, "RSA", 2048, keyName, password, "SHA1withRSA", 30,
|
||||||
distinguishedNameValues);
|
distinguishedNameValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static KeySet createKeystoreAndKey( String storePath, char[] storePass,
|
public static KeySet createKeystoreAndKey(String storePath, char[] storePass,
|
||||||
String keyAlgorithm, int keySize, String keyName, char[] keyPass,
|
String keyAlgorithm, int keySize, String keyName, char[] keyPass,
|
||||||
String certSignatureAlgorithm, int certValidityYears, DistinguishedNameValues distinguishedNameValues) {
|
String certSignatureAlgorithm, int certValidityYears, DistinguishedNameValues distinguishedNameValues) {
|
||||||
try {
|
try {
|
||||||
@ -53,20 +57,20 @@ public class CertCreator {
|
|||||||
if (sfile.exists()) {
|
if (sfile.exists()) {
|
||||||
throw new IOException("File already exists: " + storePath);
|
throw new IOException("File already exists: " + storePath);
|
||||||
}
|
}
|
||||||
KeyStoreFileManager.writeKeyStore( privateKS, storePath, storePass);
|
KeyStoreFileManager.writeKeyStore(privateKS, storePath, storePass);
|
||||||
|
|
||||||
return keySet;
|
return keySet;
|
||||||
} catch (RuntimeException x) {
|
} catch (RuntimeException x) {
|
||||||
throw x;
|
throw x;
|
||||||
} catch ( Exception x) {
|
} catch (Exception x) {
|
||||||
throw new RuntimeException( x.getMessage(), x);
|
throw new RuntimeException(x.getMessage(), x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create a new key and store it in an existing keystore.
|
/**
|
||||||
*
|
* Create a new key and store it in an existing keystore.
|
||||||
*/
|
*/
|
||||||
public static KeySet createKey( String storePath, char[] storePass,
|
public static KeySet createKey(String storePath, char[] storePass,
|
||||||
String keyAlgorithm, int keySize, String keyName, char[] keyPass,
|
String keyAlgorithm, int keySize, String keyName, char[] keyPass,
|
||||||
String certSignatureAlgorithm, int certValidityYears,
|
String certSignatureAlgorithm, int certValidityYears,
|
||||||
DistinguishedNameValues distinguishedNameValues) {
|
DistinguishedNameValues distinguishedNameValues) {
|
||||||
@ -81,20 +85,19 @@ public class CertCreator {
|
|||||||
keyPass,
|
keyPass,
|
||||||
new java.security.cert.Certificate[]{keySet.getPublicKey()});
|
new java.security.cert.Certificate[]{keySet.getPublicKey()});
|
||||||
|
|
||||||
KeyStoreFileManager.writeKeyStore( privateKS, storePath, storePass);
|
KeyStoreFileManager.writeKeyStore(privateKS, storePath, storePass);
|
||||||
|
|
||||||
return keySet;
|
return keySet;
|
||||||
|
|
||||||
} catch (RuntimeException x) {
|
} catch (RuntimeException x) {
|
||||||
throw x;
|
throw x;
|
||||||
} catch ( Exception x) {
|
} catch (Exception x) {
|
||||||
throw new RuntimeException(x.getMessage(), x);
|
throw new RuntimeException(x.getMessage(), x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static KeySet createKey( String keyAlgorithm, int keySize, String keyName,
|
public static KeySet createKey(String keyAlgorithm, int keySize, String keyName,
|
||||||
String certSignatureAlgorithm, int certValidityYears, DistinguishedNameValues distinguishedNameValues)
|
String certSignatureAlgorithm, int certValidityYears, DistinguishedNameValues distinguishedNameValues) {
|
||||||
{
|
|
||||||
try {
|
try {
|
||||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(keyAlgorithm);
|
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(keyAlgorithm);
|
||||||
keyPairGenerator.initialize(keySize);
|
keyPairGenerator.initialize(keySize);
|
||||||
@ -110,15 +113,15 @@ public class CertCreator {
|
|||||||
serialNumber = BigInteger.valueOf(new SecureRandom().nextInt());
|
serialNumber = BigInteger.valueOf(new SecureRandom().nextInt());
|
||||||
}
|
}
|
||||||
v3CertGen.setSerialNumber(serialNumber);
|
v3CertGen.setSerialNumber(serialNumber);
|
||||||
v3CertGen.setIssuerDN( principal);
|
v3CertGen.setIssuerDN(principal);
|
||||||
v3CertGen.setNotBefore(new Date(System.currentTimeMillis() - 1000L * 60L * 60L * 24L * 30L));
|
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.setSubjectDN(principal);
|
||||||
|
|
||||||
v3CertGen.setPublicKey(KPair.getPublic());
|
v3CertGen.setPublicKey(KPair.getPublic());
|
||||||
v3CertGen.setSignatureAlgorithm(certSignatureAlgorithm);
|
v3CertGen.setSignatureAlgorithm(certSignatureAlgorithm);
|
||||||
|
|
||||||
X509Certificate PKCertificate = v3CertGen.generate(KPair.getPrivate(),"BC");
|
X509Certificate PKCertificate = v3CertGen.generate(KPair.getPrivate(), "BC");
|
||||||
|
|
||||||
KeySet keySet = new KeySet();
|
KeySet keySet = new KeySet();
|
||||||
keySet.setName(keyName);
|
keySet.setName(keyName);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
package kellinwood.security.zipsigner.optional;
|
package kellinwood.security.zipsigner.optional;
|
||||||
|
|
||||||
import kellinwood.security.zipsigner.ZipSigner;
|
import kellinwood.security.zipsigner.ZipSigner;
|
||||||
@ -12,8 +13,10 @@ import java.security.cert.X509Certificate;
|
|||||||
*/
|
*/
|
||||||
public class CustomKeySigner {
|
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,
|
* KeyStore-type agnostic. This method will sign the zip file, automatically handling JKS or BKS keystores.
|
||||||
|
*/
|
||||||
|
public static void signZip(ZipSigner zipSigner,
|
||||||
String keystorePath,
|
String keystorePath,
|
||||||
char[] keystorePw,
|
char[] keystorePw,
|
||||||
String certAlias,
|
String certAlias,
|
||||||
@ -21,17 +24,16 @@ public class CustomKeySigner {
|
|||||||
String signatureAlgorithm,
|
String signatureAlgorithm,
|
||||||
String inputZipFilename,
|
String inputZipFilename,
|
||||||
String outputZipFilename)
|
String outputZipFilename)
|
||||||
throws Exception
|
throws Exception {
|
||||||
{
|
|
||||||
zipSigner.issueLoadingCertAndKeysProgressEvent();
|
zipSigner.issueLoadingCertAndKeysProgressEvent();
|
||||||
KeyStore keystore = KeyStoreFileManager.loadKeyStore( keystorePath, keystorePw);
|
KeyStore keystore = KeyStoreFileManager.loadKeyStore(keystorePath, keystorePw);
|
||||||
Certificate cert = keystore.getCertificate(certAlias);
|
Certificate cert = keystore.getCertificate(certAlias);
|
||||||
X509Certificate publicKey = (X509Certificate)cert;
|
X509Certificate publicKey = (X509Certificate) cert;
|
||||||
Key key = keystore.getKey(certAlias, certPw);
|
Key key = keystore.getKey(certAlias, certPw);
|
||||||
PrivateKey privateKey = (PrivateKey)key;
|
PrivateKey privateKey = (PrivateKey) key;
|
||||||
|
|
||||||
zipSigner.setKeys( "custom", publicKey, privateKey, signatureAlgorithm, null);
|
zipSigner.setKeys("custom", publicKey, privateKey, signatureAlgorithm, null);
|
||||||
zipSigner.signZip( inputZipFilename, outputZipFilename);
|
zipSigner.signZip(inputZipFilename, outputZipFilename);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
|
|
||||||
package kellinwood.security.zipsigner.optional;
|
package kellinwood.security.zipsigner.optional;
|
||||||
|
|
||||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||||
import org.bouncycastle.asn1.x500.style.BCStyle;
|
import org.bouncycastle.asn1.x500.style.BCStyle;
|
||||||
import org.bouncycastle.jce.X509Principal;
|
import org.bouncycastle.jce.X509Principal;
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
@ -13,61 +13,61 @@ import java.util.Vector;
|
|||||||
/**
|
/**
|
||||||
* Helper class for dealing with the distinguished name RDNs.
|
* 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() {
|
public DistinguishedNameValues() {
|
||||||
put(BCStyle.C,null);
|
put(BCStyle.C, null);
|
||||||
put(BCStyle.ST,null);
|
put(BCStyle.ST, null);
|
||||||
put(BCStyle.L,null);
|
put(BCStyle.L, null);
|
||||||
put(BCStyle.STREET,null);
|
put(BCStyle.STREET, null);
|
||||||
put(BCStyle.O,null);
|
put(BCStyle.O, null);
|
||||||
put(BCStyle.OU,null);
|
put(BCStyle.OU, null);
|
||||||
put(BCStyle.CN,null);
|
put(BCStyle.CN, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String put(ASN1ObjectIdentifier oid, String value) {
|
public String put(ASN1ObjectIdentifier oid, String value) {
|
||||||
if (value != null && value.equals("")) value = null;
|
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 {
|
else {
|
||||||
super.put(oid,value);
|
super.put(oid, value);
|
||||||
// String cn = remove(BCStyle.CN); // CN will always be last.
|
// String cn = remove(BCStyle.CN); // CN will always be last.
|
||||||
// put(BCStyle.CN,cn);
|
// put(BCStyle.CN,cn);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCountry( String country) {
|
public void setCountry(String country) {
|
||||||
put(BCStyle.C,country);
|
put(BCStyle.C, country);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setState( String state) {
|
public void setState(String state) {
|
||||||
put(BCStyle.ST,state);
|
put(BCStyle.ST, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLocality( String locality) {
|
public void setLocality(String locality) {
|
||||||
put(BCStyle.L,locality);
|
put(BCStyle.L, locality);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStreet( String street) {
|
public void setStreet(String street) {
|
||||||
put( BCStyle.STREET, street);
|
put(BCStyle.STREET, street);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOrganization( String organization) {
|
public void setOrganization(String organization) {
|
||||||
put(BCStyle.O,organization);
|
put(BCStyle.O, organization);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOrganizationalUnit( String organizationalUnit) {
|
public void setOrganizationalUnit(String organizationalUnit) {
|
||||||
put(BCStyle.OU,organizationalUnit);
|
put(BCStyle.OU, organizationalUnit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCommonName( String commonName) {
|
public void setCommonName(String commonName) {
|
||||||
put(BCStyle.CN,commonName);
|
put(BCStyle.CN, commonName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int size() {
|
public int size() {
|
||||||
int result = 0;
|
int result = 0;
|
||||||
for( String value : values()) {
|
for (String value : values()) {
|
||||||
if (value != null) result += 1;
|
if (value != null) result += 1;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -77,13 +77,13 @@ public class DistinguishedNameValues extends LinkedHashMap<ASN1ObjectIdentifier,
|
|||||||
Vector<ASN1ObjectIdentifier> oids = new Vector<ASN1ObjectIdentifier>();
|
Vector<ASN1ObjectIdentifier> oids = new Vector<ASN1ObjectIdentifier>();
|
||||||
Vector<String> values = new Vector<String>();
|
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("")) {
|
if (entry.getValue() != null && !entry.getValue().equals("")) {
|
||||||
oids.add( entry.getKey());
|
oids.add(entry.getKey());
|
||||||
values.add( entry.getValue());
|
values.add(entry.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new X509Principal(oids,values);
|
return new X509Principal(oids, values);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
package kellinwood.security.zipsigner.optional;
|
package kellinwood.security.zipsigner.optional;
|
||||||
|
|
||||||
import kellinwood.logging.LoggerInterface;
|
import kellinwood.logging.LoggerInterface;
|
||||||
@ -15,34 +16,34 @@ public class Fingerprint {
|
|||||||
|
|
||||||
static LoggerInterface logger = LoggerManager.getLogger(Fingerprint.class.getName());
|
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;
|
byte[] result = null;
|
||||||
try {
|
try {
|
||||||
MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
|
MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
|
||||||
messageDigest.update(encodedCert);
|
messageDigest.update(encodedCert);
|
||||||
result = messageDigest.digest();
|
result = messageDigest.digest();
|
||||||
} catch (Exception x) {
|
} catch (Exception x) {
|
||||||
logger.error(x.getMessage(),x);
|
logger.error(x.getMessage(), x);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String hexFingerprint( String algorithm, byte[] encodedCert) {
|
public static String hexFingerprint(String algorithm, byte[] encodedCert) {
|
||||||
try {
|
try {
|
||||||
byte[] digest = calcDigest(algorithm,encodedCert);
|
byte[] digest = calcDigest(algorithm, encodedCert);
|
||||||
if (digest == null) return null;
|
if (digest == null) return null;
|
||||||
HexTranslator hexTranslator = new HexTranslator();
|
HexTranslator hexTranslator = new HexTranslator();
|
||||||
byte[] hex = new byte[digest.length * 2];
|
byte[] hex = new byte[digest.length * 2];
|
||||||
hexTranslator.encode(digest, 0, digest.length, hex, 0);
|
hexTranslator.encode(digest, 0, digest.length, hex, 0);
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
for (int i = 0; i < hex.length; i += 2) {
|
for (int i = 0; i < hex.length; i += 2) {
|
||||||
builder.append((char)hex[i]);
|
builder.append((char) hex[i]);
|
||||||
builder.append((char)hex[i+1]);
|
builder.append((char) hex[i + 1]);
|
||||||
if (i != (hex.length - 2)) builder.append(':');
|
if (i != (hex.length - 2)) builder.append(':');
|
||||||
}
|
}
|
||||||
return builder.toString().toUpperCase();
|
return builder.toString().toUpperCase();
|
||||||
} catch (Exception x) {
|
} catch (Exception x) {
|
||||||
logger.error(x.getMessage(),x);
|
logger.error(x.getMessage(), x);
|
||||||
}
|
}
|
||||||
return null;
|
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;
|
String result = null;
|
||||||
try {
|
try {
|
||||||
byte[] digest = calcDigest(algorithm,encodedCert);
|
byte[] digest = calcDigest(algorithm, encodedCert);
|
||||||
if (digest == null) return result;
|
if (digest == null) return result;
|
||||||
return Base64.encode(digest);
|
return Base64.encode(digest);
|
||||||
} catch (Exception x) {
|
} catch (Exception x) {
|
||||||
logger.error(x.getMessage(),x);
|
logger.error(x.getMessage(), x);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -23,15 +23,17 @@ power to enforce restrictions on reverse-engineering of their software,
|
|||||||
and it is irresponsible for them to claim they can. */
|
and it is irresponsible for them to claim they can. */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package kellinwood.security.zipsigner.optional;
|
package kellinwood.security.zipsigner.optional;
|
||||||
|
|
||||||
|
import javax.crypto.EncryptedPrivateKeyInfo;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
import java.security.DigestInputStream;
|
import java.security.DigestInputStream;
|
||||||
import java.security.DigestOutputStream;
|
import java.security.DigestOutputStream;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
@ -42,29 +44,24 @@ import java.security.MessageDigest;
|
|||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.UnrecoverableKeyException;
|
import java.security.UnrecoverableKeyException;
|
||||||
|
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
|
||||||
import javax.crypto.EncryptedPrivateKeyInfo;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is an implementation of Sun's proprietary key store
|
* This is an implementation of Sun's proprietary key store
|
||||||
* algorithm, called "JKS" for "Java Key Store". This implementation was
|
* algorithm, called "JKS" for "Java Key Store". This implementation was
|
||||||
* created entirely through reverse-engineering.
|
* created entirely through reverse-engineering.
|
||||||
*
|
* <p>
|
||||||
* <p>The format of JKS files is, from the start of the file:
|
* <p>The format of JKS files is, from the start of the file:
|
||||||
*
|
* <p>
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>Magic bytes. This is a four-byte integer, in big-endian byte
|
* <li>Magic bytes. This is a four-byte integer, in big-endian byte
|
||||||
* order, equal to <code>0xFEEDFEED</code>.</li>
|
* order, equal to <code>0xFEEDFEED</code>.</li>
|
||||||
@ -82,7 +79,7 @@ import javax.crypto.spec.SecretKeySpec;
|
|||||||
* href="http://java.sun.com/j2se/1.4.1/docs/api/java/io/DataOutput.html#writeUTF(java.lang.String)">DataOutput.writeUTF(String)</a>.</li>
|
* 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,
|
* <li>An eight-byte integer, representing the entry's creation date,
|
||||||
* in milliseconds since the epoch.
|
* in milliseconds since the epoch.
|
||||||
*
|
* <p>
|
||||||
* <p>Then, if the entry is a private key entry:
|
* <p>Then, if the entry is a private key entry:
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>The size of the encoded key as a four-byte int, then that
|
* <li>The size of the encoded key as a four-byte int, then that
|
||||||
@ -94,7 +91,7 @@ import javax.crypto.spec.SecretKeySpec;
|
|||||||
* certificates, encoded as described in the trusted certificates
|
* certificates, encoded as described in the trusted certificates
|
||||||
* section.</li>
|
* section.</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
*
|
* <p>
|
||||||
* <p>Otherwise, the entry is a trusted certificate, which is encoded
|
* <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
|
* 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
|
* way as alias names. Then, a four-byte integer representing the size
|
||||||
@ -108,12 +105,12 @@ import javax.crypto.spec.SecretKeySpec;
|
|||||||
* </ol>
|
* </ol>
|
||||||
* </li>
|
* </li>
|
||||||
* </ol>
|
* </ol>
|
||||||
*
|
* <p>
|
||||||
* <p>(See <a href="genkey.java">this file</a> for some idea of how I
|
* <p>(See <a href="genkey.java">this file</a> for some idea of how I
|
||||||
* was able to figure out these algorithms)</p>
|
* was able to figure out these algorithms)</p>
|
||||||
*
|
* <p>
|
||||||
* <p>Decrypting the key works as follows:
|
* <p>Decrypting the key works as follows:
|
||||||
*
|
* <p>
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>The key length is the length of the ciphertext minus 40. The
|
* <li>The key length is the length of the ciphertext minus 40. The
|
||||||
* encrypted key, <code>ekey</code>, is the middle bytes of the
|
* encrypted key, <code>ekey</code>, is the middle bytes of the
|
||||||
@ -129,33 +126,34 @@ import javax.crypto.spec.SecretKeySpec;
|
|||||||
* the last 20 bytes of the ciphertext, output <code>FAIL</code>. Otherwise,
|
* the last 20 bytes of the ciphertext, output <code>FAIL</code>. Otherwise,
|
||||||
* output <code>key</code>.</li>
|
* output <code>key</code>.</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
*
|
* <p>
|
||||||
* <p>The signature is defined as <code>SHA-1(UTF-16BE(password) +
|
* <p>The signature is defined as <code>SHA-1(UTF-16BE(password) +
|
||||||
* US_ASCII("Mighty Aphrodite") + encoded_keystore)</code> (yup, Sun
|
* US_ASCII("Mighty Aphrodite") + encoded_keystore)</code> (yup, Sun
|
||||||
* engineers are just that clever).
|
* engineers are just that clever).
|
||||||
*
|
* <p>
|
||||||
* <p>(Above, SHA-1 denotes the secure hash algorithm, UTF-16BE the
|
* <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
|
* big-endian byte representation of a UTF-16 string, and US_ASCII the
|
||||||
* ASCII byte representation of the string.)
|
* ASCII byte representation of the string.)
|
||||||
*
|
* <p>
|
||||||
* <p>The source code of this class should be available in the file <a
|
* <p>The source code of this class should be available in the file <a
|
||||||
* href="http://metastatic.org/source/JKS.java">JKS.java</a>.
|
* href="http://metastatic.org/source/JKS.java">JKS.java</a>.
|
||||||
*
|
*
|
||||||
* @author Casey Marshall (rsdio@metastatic.org)
|
* @author Casey Marshall (rsdio@metastatic.org)
|
||||||
*
|
* <p>
|
||||||
* Changes by Ken Ellinwood:
|
* Changes by Ken Ellinwood:
|
||||||
* ** Fixed a NullPointerException in engineLoad(). This method must return gracefully if the keystore input stream is null.
|
* ** 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.
|
* ** 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.
|
* ** 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.
|
* ** 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.
|
// 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 MAGIC = 0xFEEDFEED;
|
||||||
|
|
||||||
private static final int PRIVATE_KEY = 1;
|
private static final int PRIVATE_KEY = 1;
|
||||||
@ -170,8 +168,7 @@ public class JKS extends KeyStoreSpi
|
|||||||
// Constructor.
|
// Constructor.
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
public JKS()
|
public JKS() {
|
||||||
{
|
|
||||||
super();
|
super();
|
||||||
aliases = new Vector();
|
aliases = new Vector();
|
||||||
trustedCerts = new HashMap();
|
trustedCerts = new HashMap();
|
||||||
@ -185,8 +182,7 @@ public class JKS extends KeyStoreSpi
|
|||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
public Key engineGetKey(String alias, char[] password)
|
public Key engineGetKey(String alias, char[] password)
|
||||||
throws NoSuchAlgorithmException, UnrecoverableKeyException
|
throws NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||||
{
|
|
||||||
alias = alias.toLowerCase();
|
alias = alias.toLowerCase();
|
||||||
|
|
||||||
if (!privateKeys.containsKey(alias))
|
if (!privateKeys.containsKey(alias))
|
||||||
@ -194,32 +190,25 @@ public class JKS extends KeyStoreSpi
|
|||||||
byte[] key = decryptKey((byte[]) privateKeys.get(alias),
|
byte[] key = decryptKey((byte[]) privateKeys.get(alias),
|
||||||
charsToBytes(password));
|
charsToBytes(password));
|
||||||
Certificate[] chain = engineGetCertificateChain(alias);
|
Certificate[] chain = engineGetCertificateChain(alias);
|
||||||
if (chain.length > 0)
|
if (chain.length > 0) {
|
||||||
{
|
try {
|
||||||
try
|
|
||||||
{
|
|
||||||
// Private and public keys MUST have the same algorithm.
|
// Private and public keys MUST have the same algorithm.
|
||||||
KeyFactory fact = KeyFactory.getInstance(
|
KeyFactory fact = KeyFactory.getInstance(
|
||||||
chain[0].getPublicKey().getAlgorithm());
|
chain[0].getPublicKey().getAlgorithm());
|
||||||
return fact.generatePrivate(new PKCS8EncodedKeySpec(key));
|
return fact.generatePrivate(new PKCS8EncodedKeySpec(key));
|
||||||
}
|
} catch (InvalidKeySpecException x) {
|
||||||
catch (InvalidKeySpecException x)
|
|
||||||
{
|
|
||||||
throw new UnrecoverableKeyException(x.getMessage());
|
throw new UnrecoverableKeyException(x.getMessage());
|
||||||
}
|
}
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
return new SecretKeySpec(key, alias);
|
return new SecretKeySpec(key, alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Certificate[] engineGetCertificateChain(String alias)
|
public Certificate[] engineGetCertificateChain(String alias) {
|
||||||
{
|
|
||||||
alias = alias.toLowerCase();
|
alias = alias.toLowerCase();
|
||||||
return (Certificate[]) certChains.get(alias);
|
return (Certificate[]) certChains.get(alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Certificate engineGetCertificate(String alias)
|
public Certificate engineGetCertificate(String alias) {
|
||||||
{
|
|
||||||
alias = alias.toLowerCase();
|
alias = alias.toLowerCase();
|
||||||
if (engineIsKeyEntry(alias)) {
|
if (engineIsKeyEntry(alias)) {
|
||||||
Certificate[] certChain = (Certificate[]) certChains.get(alias);
|
Certificate[] certChain = (Certificate[]) certChains.get(alias);
|
||||||
@ -228,8 +217,7 @@ public class JKS extends KeyStoreSpi
|
|||||||
return (Certificate) trustedCerts.get(alias);
|
return (Certificate) trustedCerts.get(alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Date engineGetCreationDate(String alias)
|
public Date engineGetCreationDate(String alias) {
|
||||||
{
|
|
||||||
alias = alias.toLowerCase();
|
alias = alias.toLowerCase();
|
||||||
return (Date) dates.get(alias);
|
return (Date) dates.get(alias);
|
||||||
}
|
}
|
||||||
@ -237,8 +225,7 @@ public class JKS extends KeyStoreSpi
|
|||||||
// XXX implement writing methods.
|
// XXX implement writing methods.
|
||||||
|
|
||||||
public void engineSetKeyEntry(String alias, Key key, char[] passwd, Certificate[] certChain)
|
public void engineSetKeyEntry(String alias, Key key, char[] passwd, Certificate[] certChain)
|
||||||
throws KeyStoreException
|
throws KeyStoreException {
|
||||||
{
|
|
||||||
alias = alias.toLowerCase();
|
alias = alias.toLowerCase();
|
||||||
if (trustedCerts.containsKey(alias))
|
if (trustedCerts.containsKey(alias))
|
||||||
throw new KeyStoreException("\"" + alias + " is a trusted certificate entry");
|
throw new KeyStoreException("\"" + alias + " is a trusted certificate entry");
|
||||||
@ -247,25 +234,20 @@ public class JKS extends KeyStoreSpi
|
|||||||
certChains.put(alias, certChain);
|
certChains.put(alias, certChain);
|
||||||
else
|
else
|
||||||
certChains.put(alias, new Certificate[0]);
|
certChains.put(alias, new Certificate[0]);
|
||||||
if (!aliases.contains(alias))
|
if (!aliases.contains(alias)) {
|
||||||
{
|
|
||||||
dates.put(alias, new Date());
|
dates.put(alias, new Date());
|
||||||
aliases.add(alias);
|
aliases.add(alias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void engineSetKeyEntry(String alias, byte[] encodedKey, Certificate[] certChain)
|
public void engineSetKeyEntry(String alias, byte[] encodedKey, Certificate[] certChain)
|
||||||
throws KeyStoreException
|
throws KeyStoreException {
|
||||||
{
|
|
||||||
alias = alias.toLowerCase();
|
alias = alias.toLowerCase();
|
||||||
if (trustedCerts.containsKey(alias))
|
if (trustedCerts.containsKey(alias))
|
||||||
throw new KeyStoreException("\"" + alias + "\" is a trusted certificate entry");
|
throw new KeyStoreException("\"" + alias + "\" is a trusted certificate entry");
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
new EncryptedPrivateKeyInfo(encodedKey);
|
new EncryptedPrivateKeyInfo(encodedKey);
|
||||||
}
|
} catch (IOException ioe) {
|
||||||
catch (IOException ioe)
|
|
||||||
{
|
|
||||||
throw new KeyStoreException("encoded key is not an EncryptedPrivateKeyInfo");
|
throw new KeyStoreException("encoded key is not an EncryptedPrivateKeyInfo");
|
||||||
}
|
}
|
||||||
privateKeys.put(alias, encodedKey);
|
privateKeys.put(alias, encodedKey);
|
||||||
@ -273,67 +255,56 @@ public class JKS extends KeyStoreSpi
|
|||||||
certChains.put(alias, certChain);
|
certChains.put(alias, certChain);
|
||||||
else
|
else
|
||||||
certChains.put(alias, new Certificate[0]);
|
certChains.put(alias, new Certificate[0]);
|
||||||
if (!aliases.contains(alias))
|
if (!aliases.contains(alias)) {
|
||||||
{
|
|
||||||
dates.put(alias, new Date());
|
dates.put(alias, new Date());
|
||||||
aliases.add(alias);
|
aliases.add(alias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void engineSetCertificateEntry(String alias, Certificate cert)
|
public void engineSetCertificateEntry(String alias, Certificate cert)
|
||||||
throws KeyStoreException
|
throws KeyStoreException {
|
||||||
{
|
|
||||||
alias = alias.toLowerCase();
|
alias = alias.toLowerCase();
|
||||||
if (privateKeys.containsKey(alias))
|
if (privateKeys.containsKey(alias))
|
||||||
throw new KeyStoreException("\"" + alias + "\" is a private key entry");
|
throw new KeyStoreException("\"" + alias + "\" is a private key entry");
|
||||||
if (cert == null)
|
if (cert == null)
|
||||||
throw new NullPointerException();
|
throw new NullPointerException();
|
||||||
trustedCerts.put(alias, cert);
|
trustedCerts.put(alias, cert);
|
||||||
if (!aliases.contains(alias))
|
if (!aliases.contains(alias)) {
|
||||||
{
|
|
||||||
dates.put(alias, new Date());
|
dates.put(alias, new Date());
|
||||||
aliases.add(alias);
|
aliases.add(alias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void engineDeleteEntry(String alias) throws KeyStoreException
|
public void engineDeleteEntry(String alias) throws KeyStoreException {
|
||||||
{
|
|
||||||
alias = alias.toLowerCase();
|
alias = alias.toLowerCase();
|
||||||
aliases.remove(alias);
|
aliases.remove(alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Enumeration engineAliases()
|
public Enumeration engineAliases() {
|
||||||
{
|
|
||||||
return aliases.elements();
|
return aliases.elements();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean engineContainsAlias(String alias)
|
public boolean engineContainsAlias(String alias) {
|
||||||
{
|
|
||||||
alias = alias.toLowerCase();
|
alias = alias.toLowerCase();
|
||||||
return aliases.contains(alias);
|
return aliases.contains(alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int engineSize()
|
public int engineSize() {
|
||||||
{
|
|
||||||
return aliases.size();
|
return aliases.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean engineIsKeyEntry(String alias)
|
public boolean engineIsKeyEntry(String alias) {
|
||||||
{
|
|
||||||
alias = alias.toLowerCase();
|
alias = alias.toLowerCase();
|
||||||
return privateKeys.containsKey(alias);
|
return privateKeys.containsKey(alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean engineIsCertificateEntry(String alias)
|
public boolean engineIsCertificateEntry(String alias) {
|
||||||
{
|
|
||||||
alias = alias.toLowerCase();
|
alias = alias.toLowerCase();
|
||||||
return trustedCerts.containsKey(alias);
|
return trustedCerts.containsKey(alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String engineGetCertificateAlias(Certificate cert)
|
public String engineGetCertificateAlias(Certificate cert) {
|
||||||
{
|
for (Iterator keys = trustedCerts.keySet().iterator(); keys.hasNext(); ) {
|
||||||
for (Iterator keys = trustedCerts.keySet().iterator(); keys.hasNext(); )
|
|
||||||
{
|
|
||||||
String alias = (String) keys.next();
|
String alias = (String) keys.next();
|
||||||
if (cert.equals(trustedCerts.get(alias)))
|
if (cert.equals(trustedCerts.get(alias)))
|
||||||
return alias;
|
return alias;
|
||||||
@ -342,8 +313,7 @@ public class JKS extends KeyStoreSpi
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void engineStore(OutputStream out, char[] passwd)
|
public void engineStore(OutputStream out, char[] passwd)
|
||||||
throws IOException, NoSuchAlgorithmException, CertificateException
|
throws IOException, NoSuchAlgorithmException, CertificateException {
|
||||||
{
|
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA1");
|
MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||||
md.update(charsToBytes(passwd));
|
md.update(charsToBytes(passwd));
|
||||||
md.update("Mighty Aphrodite".getBytes("UTF-8"));
|
md.update("Mighty Aphrodite".getBytes("UTF-8"));
|
||||||
@ -351,18 +321,14 @@ public class JKS extends KeyStoreSpi
|
|||||||
dout.writeInt(MAGIC);
|
dout.writeInt(MAGIC);
|
||||||
dout.writeInt(2);
|
dout.writeInt(2);
|
||||||
dout.writeInt(aliases.size());
|
dout.writeInt(aliases.size());
|
||||||
for (Enumeration e = aliases.elements(); e.hasMoreElements(); )
|
for (Enumeration e = aliases.elements(); e.hasMoreElements(); ) {
|
||||||
{
|
|
||||||
String alias = (String) e.nextElement();
|
String alias = (String) e.nextElement();
|
||||||
if (trustedCerts.containsKey(alias))
|
if (trustedCerts.containsKey(alias)) {
|
||||||
{
|
|
||||||
dout.writeInt(TRUSTED_CERT);
|
dout.writeInt(TRUSTED_CERT);
|
||||||
dout.writeUTF(alias);
|
dout.writeUTF(alias);
|
||||||
dout.writeLong(((Date) dates.get(alias)).getTime());
|
dout.writeLong(((Date) dates.get(alias)).getTime());
|
||||||
writeCert(dout, (Certificate) trustedCerts.get(alias));
|
writeCert(dout, (Certificate) trustedCerts.get(alias));
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
dout.writeInt(PRIVATE_KEY);
|
dout.writeInt(PRIVATE_KEY);
|
||||||
dout.writeUTF(alias);
|
dout.writeUTF(alias);
|
||||||
dout.writeLong(((Date) dates.get(alias)).getTime());
|
dout.writeLong(((Date) dates.get(alias)).getTime());
|
||||||
@ -380,8 +346,7 @@ public class JKS extends KeyStoreSpi
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void engineLoad(InputStream in, char[] passwd)
|
public void engineLoad(InputStream in, char[] passwd)
|
||||||
throws IOException, NoSuchAlgorithmException, CertificateException
|
throws IOException, NoSuchAlgorithmException, CertificateException {
|
||||||
{
|
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA");
|
MessageDigest md = MessageDigest.getInstance("SHA");
|
||||||
if (passwd != null) md.update(charsToBytes(passwd));
|
if (passwd != null) md.update(charsToBytes(passwd));
|
||||||
md.update("Mighty Aphrodite".getBytes("UTF-8")); // HAR HAR
|
md.update("Mighty Aphrodite".getBytes("UTF-8")); // HAR HAR
|
||||||
@ -399,14 +364,12 @@ public class JKS extends KeyStoreSpi
|
|||||||
aliases.ensureCapacity(n);
|
aliases.ensureCapacity(n);
|
||||||
if (n < 0)
|
if (n < 0)
|
||||||
throw new LoadKeystoreException("Malformed key store");
|
throw new LoadKeystoreException("Malformed key store");
|
||||||
for (int i = 0; i < n; i++)
|
for (int i = 0; i < n; i++) {
|
||||||
{
|
|
||||||
int type = din.readInt();
|
int type = din.readInt();
|
||||||
String alias = din.readUTF();
|
String alias = din.readUTF();
|
||||||
aliases.add(alias);
|
aliases.add(alias);
|
||||||
dates.put(alias, new Date(din.readLong()));
|
dates.put(alias, new Date(din.readLong()));
|
||||||
switch (type)
|
switch (type) {
|
||||||
{
|
|
||||||
case PRIVATE_KEY:
|
case PRIVATE_KEY:
|
||||||
int len = din.readInt();
|
int len = din.readInt();
|
||||||
byte[] encoded = new byte[len];
|
byte[] encoded = new byte[len];
|
||||||
@ -442,8 +405,7 @@ public class JKS extends KeyStoreSpi
|
|||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
private static Certificate readCert(DataInputStream in)
|
private static Certificate readCert(DataInputStream in)
|
||||||
throws IOException, CertificateException, NoSuchAlgorithmException
|
throws IOException, CertificateException, NoSuchAlgorithmException {
|
||||||
{
|
|
||||||
String type = in.readUTF();
|
String type = in.readUTF();
|
||||||
int len = in.readInt();
|
int len = in.readInt();
|
||||||
byte[] encoded = new byte[len];
|
byte[] encoded = new byte[len];
|
||||||
@ -453,8 +415,7 @@ public class JKS extends KeyStoreSpi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void writeCert(DataOutputStream dout, Certificate cert)
|
private static void writeCert(DataOutputStream dout, Certificate cert)
|
||||||
throws IOException, CertificateException
|
throws IOException, CertificateException {
|
||||||
{
|
|
||||||
dout.writeUTF(cert.getType());
|
dout.writeUTF(cert.getType());
|
||||||
byte[] b = cert.getEncoded();
|
byte[] b = cert.getEncoded();
|
||||||
dout.writeInt(b.length);
|
dout.writeInt(b.length);
|
||||||
@ -462,29 +423,25 @@ public class JKS extends KeyStoreSpi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] decryptKey(byte[] encryptedPKI, byte[] passwd)
|
private static byte[] decryptKey(byte[] encryptedPKI, byte[] passwd)
|
||||||
throws UnrecoverableKeyException
|
throws UnrecoverableKeyException {
|
||||||
{
|
try {
|
||||||
try
|
|
||||||
{
|
|
||||||
EncryptedPrivateKeyInfo epki =
|
EncryptedPrivateKeyInfo epki =
|
||||||
new EncryptedPrivateKeyInfo(encryptedPKI);
|
new EncryptedPrivateKeyInfo(encryptedPKI);
|
||||||
byte[] encr = epki.getEncryptedData();
|
byte[] encr = epki.getEncryptedData();
|
||||||
byte[] keystream = new byte[20];
|
byte[] keystream = new byte[20];
|
||||||
System.arraycopy(encr, 0, keystream, 0, 20);
|
System.arraycopy(encr, 0, keystream, 0, 20);
|
||||||
byte[] check = new byte[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];
|
byte[] key = new byte[encr.length - 40];
|
||||||
MessageDigest sha = MessageDigest.getInstance("SHA1");
|
MessageDigest sha = MessageDigest.getInstance("SHA1");
|
||||||
int count = 0;
|
int count = 0;
|
||||||
while (count < key.length)
|
while (count < key.length) {
|
||||||
{
|
|
||||||
sha.reset();
|
sha.reset();
|
||||||
sha.update(passwd);
|
sha.update(passwd);
|
||||||
sha.update(keystream);
|
sha.update(keystream);
|
||||||
sha.digest(keystream, 0, keystream.length);
|
sha.digest(keystream, 0, keystream.length);
|
||||||
for (int i = 0; i < keystream.length && count < key.length; i++)
|
for (int i = 0; i < keystream.length && count < key.length; i++) {
|
||||||
{
|
key[count] = (byte) (keystream[i] ^ encr[count + 20]);
|
||||||
key[count] = (byte) (keystream[i] ^ encr[count+20]);
|
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -494,18 +451,14 @@ public class JKS extends KeyStoreSpi
|
|||||||
if (!MessageDigest.isEqual(check, sha.digest()))
|
if (!MessageDigest.isEqual(check, sha.digest()))
|
||||||
throw new UnrecoverableKeyException("checksum mismatch");
|
throw new UnrecoverableKeyException("checksum mismatch");
|
||||||
return key;
|
return key;
|
||||||
}
|
} catch (Exception x) {
|
||||||
catch (Exception x)
|
|
||||||
{
|
|
||||||
throw new UnrecoverableKeyException(x.getMessage());
|
throw new UnrecoverableKeyException(x.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] encryptKey(Key key, byte[] passwd)
|
private static byte[] encryptKey(Key key, byte[] passwd)
|
||||||
throws KeyStoreException
|
throws KeyStoreException {
|
||||||
{
|
try {
|
||||||
try
|
|
||||||
{
|
|
||||||
MessageDigest sha = MessageDigest.getInstance("SHA1");
|
MessageDigest sha = MessageDigest.getInstance("SHA1");
|
||||||
SecureRandom rand = SecureRandom.getInstance("SHA1PRNG");
|
SecureRandom rand = SecureRandom.getInstance("SHA1PRNG");
|
||||||
byte[] k = key.getEncoded();
|
byte[] k = key.getEncoded();
|
||||||
@ -513,15 +466,13 @@ public class JKS extends KeyStoreSpi
|
|||||||
byte[] keystream = rand.getSeed(20);
|
byte[] keystream = rand.getSeed(20);
|
||||||
System.arraycopy(keystream, 0, encrypted, 0, 20);
|
System.arraycopy(keystream, 0, encrypted, 0, 20);
|
||||||
int count = 0;
|
int count = 0;
|
||||||
while (count < k.length)
|
while (count < k.length) {
|
||||||
{
|
|
||||||
sha.reset();
|
sha.reset();
|
||||||
sha.update(passwd);
|
sha.update(passwd);
|
||||||
sha.update(keystream);
|
sha.update(keystream);
|
||||||
sha.digest(keystream, 0, keystream.length);
|
sha.digest(keystream, 0, keystream.length);
|
||||||
for (int i = 0; i < keystream.length && count < k.length; i++)
|
for (int i = 0; i < keystream.length && count < k.length; i++) {
|
||||||
{
|
encrypted[count + 20] = (byte) (keystream[i] ^ k[count]);
|
||||||
encrypted[count+20] = (byte) (keystream[i] ^ k[count]);
|
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -533,18 +484,14 @@ public class JKS extends KeyStoreSpi
|
|||||||
// encryption algorithm.
|
// encryption algorithm.
|
||||||
return new EncryptedPrivateKeyInfo("1.3.6.1.4.1.42.2.17.1.1",
|
return new EncryptedPrivateKeyInfo("1.3.6.1.4.1.42.2.17.1.1",
|
||||||
encrypted).getEncoded();
|
encrypted).getEncoded();
|
||||||
}
|
} catch (Exception x) {
|
||||||
catch (Exception x)
|
|
||||||
{
|
|
||||||
throw new KeyStoreException(x.getMessage());
|
throw new KeyStoreException(x.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] charsToBytes(char[] passwd)
|
private static byte[] charsToBytes(char[] passwd) {
|
||||||
{
|
|
||||||
byte[] buf = new byte[passwd.length * 2];
|
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] >>> 8);
|
||||||
buf[j++] = (byte) passwd[i];
|
buf[j++] = (byte) passwd[i];
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
package kellinwood.security.zipsigner.optional;
|
package kellinwood.security.zipsigner.optional;
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
package kellinwood.security.zipsigner.optional;
|
package kellinwood.security.zipsigner.optional;
|
||||||
|
|
||||||
public class KeyNameConflictException extends Exception {
|
public class KeyNameConflictException extends Exception {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
package kellinwood.security.zipsigner.optional;
|
package kellinwood.security.zipsigner.optional;
|
||||||
|
|
||||||
|
|
||||||
@ -5,27 +6,36 @@ import kellinwood.logging.LoggerInterface;
|
|||||||
import kellinwood.logging.LoggerManager;
|
import kellinwood.logging.LoggerManager;
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.File;
|
||||||
import java.security.*;
|
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;
|
import java.security.cert.Certificate;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
public class KeyStoreFileManager {
|
public class KeyStoreFileManager {
|
||||||
|
|
||||||
static Provider provider = new BouncyCastleProvider();
|
static Provider provider = new BouncyCastleProvider();
|
||||||
|
|
||||||
public static Provider getProvider() { return provider; }
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
static {
|
||||||
// Add the bouncycastle version of the BC provider so that the implementation classes returned
|
// 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)
|
public static KeyStore loadKeyStore(String keystorePath, String encodedPassword)
|
||||||
throws Exception{
|
throws Exception {
|
||||||
char password[] = null;
|
char password[] = null;
|
||||||
try {
|
try {
|
||||||
if (encodedPassword != null) {
|
if (encodedPassword != null) {
|
||||||
password = PasswordObfuscator.getInstance().decodeKeystorePassword( keystorePath, encodedPassword);
|
password = PasswordObfuscator.getInstance().decodeKeystorePassword(keystorePath, encodedPassword);
|
||||||
}
|
}
|
||||||
return loadKeyStore(keystorePath, password);
|
return loadKeyStore(keystorePath, password);
|
||||||
} finally {
|
} finally {
|
||||||
@ -47,27 +57,24 @@ public class KeyStoreFileManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static KeyStore createKeyStore( String keystorePath, char[] password)
|
public static KeyStore createKeyStore(String keystorePath, char[] password)
|
||||||
throws Exception
|
throws Exception {
|
||||||
{
|
|
||||||
KeyStore ks = null;
|
KeyStore ks = null;
|
||||||
if (keystorePath.toLowerCase().endsWith(".bks")) {
|
if (keystorePath.toLowerCase().endsWith(".bks")) {
|
||||||
ks = KeyStore.getInstance("bks", new BouncyCastleProvider());
|
ks = KeyStore.getInstance("bks", new BouncyCastleProvider());
|
||||||
}
|
} else ks = new JksKeyStore();
|
||||||
else ks = new JksKeyStore();
|
|
||||||
ks.load(null, password);
|
ks.load(null, password);
|
||||||
|
|
||||||
return ks;
|
return ks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static KeyStore loadKeyStore( String keystorePath, char[] password)
|
public static KeyStore loadKeyStore(String keystorePath, char[] password)
|
||||||
throws Exception
|
throws Exception {
|
||||||
{
|
|
||||||
KeyStore ks = null;
|
KeyStore ks = null;
|
||||||
try {
|
try {
|
||||||
ks = new JksKeyStore();
|
ks = new JksKeyStore();
|
||||||
FileInputStream fis = new FileInputStream( keystorePath);
|
FileInputStream fis = new FileInputStream(keystorePath);
|
||||||
ks.load( fis, password);
|
ks.load(fis, password);
|
||||||
fis.close();
|
fis.close();
|
||||||
return ks;
|
return ks;
|
||||||
} catch (LoadKeystoreException x) {
|
} catch (LoadKeystoreException x) {
|
||||||
@ -78,8 +85,8 @@ public class KeyStoreFileManager {
|
|||||||
// logger.warning( x.getMessage(), x);
|
// logger.warning( x.getMessage(), x);
|
||||||
try {
|
try {
|
||||||
ks = KeyStore.getInstance("bks", getProvider());
|
ks = KeyStore.getInstance("bks", getProvider());
|
||||||
FileInputStream fis = new FileInputStream( keystorePath);
|
FileInputStream fis = new FileInputStream(keystorePath);
|
||||||
ks.load( fis, password);
|
ks.load(fis, password);
|
||||||
fis.close();
|
fis.close();
|
||||||
return ks;
|
return ks;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -88,29 +95,27 @@ public class KeyStoreFileManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void writeKeyStore( KeyStore ks, String keystorePath, String encodedPassword)
|
public static void writeKeyStore(KeyStore ks, String keystorePath, String encodedPassword)
|
||||||
throws Exception
|
throws Exception {
|
||||||
{
|
|
||||||
char password[] = null;
|
char password[] = null;
|
||||||
try {
|
try {
|
||||||
password = PasswordObfuscator.getInstance().decodeKeystorePassword( keystorePath, encodedPassword);
|
password = PasswordObfuscator.getInstance().decodeKeystorePassword(keystorePath, encodedPassword);
|
||||||
writeKeyStore( ks, keystorePath, password);
|
writeKeyStore(ks, keystorePath, password);
|
||||||
} finally {
|
} finally {
|
||||||
if (password != null) PasswordObfuscator.flush(password);
|
if (password != null) PasswordObfuscator.flush(password);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void writeKeyStore( KeyStore ks, String keystorePath, char[] password)
|
public static void writeKeyStore(KeyStore ks, String keystorePath, char[] password)
|
||||||
throws Exception
|
throws Exception {
|
||||||
{
|
|
||||||
|
|
||||||
File keystoreFile = new File( keystorePath);
|
File keystoreFile = new File(keystorePath);
|
||||||
try {
|
try {
|
||||||
if (keystoreFile.exists()) {
|
if (keystoreFile.exists()) {
|
||||||
// I've had some trouble saving new verisons of the keystore file in which the file becomes empty/corrupt.
|
// 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.
|
// 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());
|
File tmpFile = File.createTempFile(keystoreFile.getName(), null, keystoreFile.getParentFile());
|
||||||
FileOutputStream fos = new FileOutputStream( tmpFile);
|
FileOutputStream fos = new FileOutputStream(tmpFile);
|
||||||
ks.store(fos, password);
|
ks.store(fos, password);
|
||||||
fos.flush();
|
fos.flush();
|
||||||
fos.close();
|
fos.close();
|
||||||
@ -125,18 +130,19 @@ public class KeyStoreFileManager {
|
|||||||
*/
|
*/
|
||||||
renameTo(tmpFile, keystoreFile);
|
renameTo(tmpFile, keystoreFile);
|
||||||
} else {
|
} else {
|
||||||
FileOutputStream fos = new FileOutputStream( keystorePath);
|
FileOutputStream fos = new FileOutputStream(keystorePath);
|
||||||
ks.store(fos, password);
|
ks.store(fos, password);
|
||||||
fos.close();
|
fos.close();
|
||||||
}
|
}
|
||||||
} catch (Exception x) {
|
} catch (Exception x) {
|
||||||
try {
|
try {
|
||||||
File logfile = File.createTempFile("zipsigner-error", ".log", keystoreFile.getParentFile());
|
File logfile = File.createTempFile("zipsigner-error", ".log", keystoreFile.getParentFile());
|
||||||
PrintWriter pw = new PrintWriter(new FileWriter( logfile));
|
PrintWriter pw = new PrintWriter(new FileWriter(logfile));
|
||||||
x.printStackTrace( pw);
|
x.printStackTrace(pw);
|
||||||
pw.flush();
|
pw.flush();
|
||||||
pw.close();
|
pw.close();
|
||||||
} catch (Exception y) {}
|
} catch (Exception y) {
|
||||||
|
}
|
||||||
throw x;
|
throw x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,10 +165,16 @@ public class KeyStoreFileManager {
|
|||||||
count += n;
|
count += n;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
try { output.close(); } catch (IOException x) {} // Ignore
|
try {
|
||||||
|
output.close();
|
||||||
|
} catch (IOException x) {
|
||||||
|
} // Ignore
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
try { input.close(); } catch (IOException x) {}
|
try {
|
||||||
|
input.close();
|
||||||
|
} catch (IOException x) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (srcFile.length() != destFile.length()) {
|
if (srcFile.length() != destFile.length()) {
|
||||||
@ -176,23 +188,20 @@ public class KeyStoreFileManager {
|
|||||||
|
|
||||||
|
|
||||||
public static void renameTo(File fromFile, File toFile)
|
public static void renameTo(File fromFile, File toFile)
|
||||||
throws IOException
|
throws IOException {
|
||||||
{
|
|
||||||
copyFile(fromFile, toFile, true);
|
copyFile(fromFile, toFile, true);
|
||||||
if (!fromFile.delete()) throw new IOException("Failed to delete " + fromFile);
|
if (!fromFile.delete()) throw new IOException("Failed to delete " + fromFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void deleteKey(String storePath, String storePass, String keyName)
|
public static void deleteKey(String storePath, String storePass, String keyName)
|
||||||
throws Exception
|
throws Exception {
|
||||||
{
|
KeyStore ks = loadKeyStore(storePath, storePass);
|
||||||
KeyStore ks = loadKeyStore( storePath, storePass);
|
ks.deleteEntry(keyName);
|
||||||
ks.deleteEntry( keyName);
|
|
||||||
writeKeyStore(ks, storePath, storePass);
|
writeKeyStore(ks, storePath, storePass);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String renameKey( String keystorePath, String storePass, String oldKeyName, String newKeyName, String keyPass)
|
public static String renameKey(String keystorePath, String storePass, String oldKeyName, String newKeyName, String keyPass)
|
||||||
throws Exception
|
throws Exception {
|
||||||
{
|
|
||||||
char[] keyPw = null;
|
char[] keyPw = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -201,59 +210,53 @@ public class KeyStoreFileManager {
|
|||||||
|
|
||||||
if (ks.containsAlias(newKeyName)) throw new KeyNameConflictException();
|
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);
|
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.setKeyEntry(newKeyName, key, keyPw, new Certificate[]{cert});
|
||||||
ks.deleteEntry( oldKeyName);
|
ks.deleteEntry(oldKeyName);
|
||||||
|
|
||||||
writeKeyStore(ks, keystorePath, storePass);
|
writeKeyStore(ks, keystorePath, storePass);
|
||||||
return newKeyName;
|
return newKeyName;
|
||||||
}
|
} finally {
|
||||||
finally {
|
|
||||||
PasswordObfuscator.flush(keyPw);
|
PasswordObfuscator.flush(keyPw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static KeyStore.Entry getKeyEntry( String keystorePath, String storePass, String keyName, String keyPass)
|
public static KeyStore.Entry getKeyEntry(String keystorePath, String storePass, String keyName, String keyPass)
|
||||||
throws Exception
|
throws Exception {
|
||||||
{
|
|
||||||
char[] keyPw = null;
|
char[] keyPw = null;
|
||||||
KeyStore.PasswordProtection passwordProtection = null;
|
KeyStore.PasswordProtection passwordProtection = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
KeyStore ks = loadKeyStore(keystorePath, storePass);
|
KeyStore ks = loadKeyStore(keystorePath, storePass);
|
||||||
keyPw = PasswordObfuscator.getInstance().decodeAliasPassword( keystorePath, keyName, keyPass);
|
keyPw = PasswordObfuscator.getInstance().decodeAliasPassword(keystorePath, keyName, keyPass);
|
||||||
passwordProtection = new KeyStore.PasswordProtection(keyPw);
|
passwordProtection = new KeyStore.PasswordProtection(keyPw);
|
||||||
return ks.getEntry( keyName, passwordProtection);
|
return ks.getEntry(keyName, passwordProtection);
|
||||||
}
|
} finally {
|
||||||
finally {
|
|
||||||
if (keyPw != null) PasswordObfuscator.flush(keyPw);
|
if (keyPw != null) PasswordObfuscator.flush(keyPw);
|
||||||
if (passwordProtection != null) passwordProtection.destroy();
|
if (passwordProtection != null) passwordProtection.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean containsKey( String keystorePath, String storePass, String keyName)
|
public static boolean containsKey(String keystorePath, String storePass, String keyName)
|
||||||
throws Exception
|
throws Exception {
|
||||||
{
|
|
||||||
KeyStore ks = loadKeyStore(keystorePath, storePass);
|
KeyStore ks = loadKeyStore(keystorePath, storePass);
|
||||||
return ks.containsAlias( keyName);
|
return ks.containsAlias(keyName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param keystorePath
|
* @param keystorePath
|
||||||
* @param encodedPassword
|
* @param encodedPassword
|
||||||
* @throws Exception if the password is invalid
|
* @throws Exception if the password is invalid
|
||||||
*/
|
*/
|
||||||
public static void validateKeystorePassword( String keystorePath, String encodedPassword)
|
public static void validateKeystorePassword(String keystorePath, String encodedPassword)
|
||||||
throws Exception
|
throws Exception {
|
||||||
{
|
|
||||||
char[] password = null;
|
char[] password = null;
|
||||||
try {
|
try {
|
||||||
KeyStore ks = KeyStoreFileManager.loadKeyStore( keystorePath, encodedPassword);
|
KeyStore ks = KeyStoreFileManager.loadKeyStore(keystorePath, encodedPassword);
|
||||||
} finally {
|
} finally {
|
||||||
if (password != null) PasswordObfuscator.flush(password);
|
if (password != null) PasswordObfuscator.flush(password);
|
||||||
}
|
}
|
||||||
@ -261,19 +264,17 @@ public class KeyStoreFileManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param keystorePath
|
* @param keystorePath
|
||||||
* @param keyName
|
* @param keyName
|
||||||
* @param encodedPassword
|
* @param encodedPassword
|
||||||
* @throws java.security.UnrecoverableKeyException if the password is invalid
|
* @throws java.security.UnrecoverableKeyException if the password is invalid
|
||||||
*/
|
*/
|
||||||
public static void validateKeyPassword( String keystorePath, String keyName, String encodedPassword)
|
public static void validateKeyPassword(String keystorePath, String keyName, String encodedPassword)
|
||||||
throws Exception
|
throws Exception {
|
||||||
{
|
|
||||||
char[] password = null;
|
char[] password = null;
|
||||||
try {
|
try {
|
||||||
KeyStore ks = KeyStoreFileManager.loadKeyStore( keystorePath, (char[])null);
|
KeyStore ks = KeyStoreFileManager.loadKeyStore(keystorePath, (char[]) null);
|
||||||
password = PasswordObfuscator.getInstance().decodeAliasPassword(keystorePath,keyName, encodedPassword);
|
password = PasswordObfuscator.getInstance().decodeAliasPassword(keystorePath, keyName, encodedPassword);
|
||||||
ks.getKey(keyName, password);
|
ks.getKey(keyName, password);
|
||||||
} finally {
|
} finally {
|
||||||
if (password != null) PasswordObfuscator.flush(password);
|
if (password != null) PasswordObfuscator.flush(password);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
package kellinwood.security.zipsigner.optional;
|
package kellinwood.security.zipsigner.optional;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
package kellinwood.security.zipsigner.optional;
|
package kellinwood.security.zipsigner.optional;
|
||||||
|
|
||||||
import kellinwood.logging.LoggerInterface;
|
import kellinwood.logging.LoggerInterface;
|
||||||
@ -6,7 +7,12 @@ import kellinwood.security.zipsigner.Base64;
|
|||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
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 {
|
public class PasswordObfuscator {
|
||||||
|
|
||||||
@ -18,7 +24,7 @@ public class PasswordObfuscator {
|
|||||||
SecretKeySpec skeySpec;
|
SecretKeySpec skeySpec;
|
||||||
|
|
||||||
private PasswordObfuscator() {
|
private PasswordObfuscator() {
|
||||||
logger = LoggerManager.getLogger( PasswordObfuscator.class.getName());
|
logger = LoggerManager.getLogger(PasswordObfuscator.class.getName());
|
||||||
skeySpec = new SecretKeySpec(x.getBytes(), "AES");
|
skeySpec = new SecretKeySpec(x.getBytes(), "AES");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,39 +33,39 @@ public class PasswordObfuscator {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String encodeKeystorePassword( String keystorePath, String password) {
|
public String encodeKeystorePassword(String keystorePath, String password) {
|
||||||
return encode( keystorePath, password);
|
return encode(keystorePath, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String encodeKeystorePassword( String keystorePath, char[] password) {
|
public String encodeKeystorePassword(String keystorePath, char[] password) {
|
||||||
return encode( keystorePath, password);
|
return encode(keystorePath, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String encodeAliasPassword( String keystorePath, String aliasName, String password) {
|
public String encodeAliasPassword(String keystorePath, String aliasName, String password) {
|
||||||
return encode( keystorePath+aliasName, password);
|
return encode(keystorePath + aliasName, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String encodeAliasPassword( String keystorePath, String aliasName, char[] password) {
|
public String encodeAliasPassword(String keystorePath, String aliasName, char[] password) {
|
||||||
return encode( keystorePath+aliasName, password);
|
return encode(keystorePath + aliasName, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
public char[] decodeKeystorePassword( String keystorePath, String password) {
|
public char[] decodeKeystorePassword(String keystorePath, String password) {
|
||||||
return decode(keystorePath,password);
|
return decode(keystorePath, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
public char[] decodeAliasPassword( String keystorePath, String aliasName, String password) {
|
public char[] decodeAliasPassword(String keystorePath, String aliasName, String password) {
|
||||||
return decode(keystorePath+aliasName,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;
|
if (password == null) return null;
|
||||||
char[] c = password.toCharArray();
|
char[] c = password.toCharArray();
|
||||||
String result = encode( junk, c);
|
String result = encode(junk, c);
|
||||||
flush(c);
|
flush(c);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String encode( String junk, char[] password) {
|
public String encode(String junk, char[] password) {
|
||||||
if (password == null) return null;
|
if (password == null) return null;
|
||||||
try {
|
try {
|
||||||
// Instantiate the cipher
|
// Instantiate the cipher
|
||||||
@ -70,33 +76,33 @@ public class PasswordObfuscator {
|
|||||||
w.write(junk);
|
w.write(junk);
|
||||||
w.write(password);
|
w.write(password);
|
||||||
w.flush();
|
w.flush();
|
||||||
byte[] encoded = cipher.doFinal( baos.toByteArray());
|
byte[] encoded = cipher.doFinal(baos.toByteArray());
|
||||||
return Base64.encode( encoded);
|
return Base64.encode(encoded);
|
||||||
} catch (Exception x) {
|
} catch (Exception x) {
|
||||||
logger.error("Failed to obfuscate password", x);
|
logger.error("Failed to obfuscate password", x);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public char[] decode( String junk, String password) {
|
public char[] decode(String junk, String password) {
|
||||||
if (password == null) return null;
|
if (password == null) return null;
|
||||||
try {
|
try {
|
||||||
// Instantiate the cipher
|
// Instantiate the cipher
|
||||||
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
|
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
|
||||||
SecretKeySpec skeySpec = new SecretKeySpec(x.getBytes(), "AES");
|
SecretKeySpec skeySpec = new SecretKeySpec(x.getBytes(), "AES");
|
||||||
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
|
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
|
||||||
byte[] bytes = cipher.doFinal( Base64.decode(password.getBytes()));
|
byte[] bytes = cipher.doFinal(Base64.decode(password.getBytes()));
|
||||||
BufferedReader r = new BufferedReader( new InputStreamReader( new ByteArrayInputStream( bytes)));
|
BufferedReader r = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes)));
|
||||||
char[] cb = new char[128];
|
char[] cb = new char[128];
|
||||||
int length = 0;
|
int length = 0;
|
||||||
int numRead;
|
int numRead;
|
||||||
while ((numRead = r.read(cb, length, 128-length)) != -1) {
|
while ((numRead = r.read(cb, length, 128 - length)) != -1) {
|
||||||
length += numRead;
|
length += numRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (length <= junk.length()) return null;
|
if (length <= junk.length()) return null;
|
||||||
|
|
||||||
char[] result = new char[ length - junk.length()];
|
char[] result = new char[length - junk.length()];
|
||||||
int j = 0;
|
int j = 0;
|
||||||
for (int i = junk.length(); i < length; i++) {
|
for (int i = junk.length(); i < length; i++) {
|
||||||
result[j] = cb[i];
|
result[j] = cb[i];
|
||||||
@ -111,14 +117,14 @@ public class PasswordObfuscator {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void flush( char[] charArray) {
|
public static void flush(char[] charArray) {
|
||||||
if (charArray == null) return;
|
if (charArray == null) return;
|
||||||
for (int i = 0; i < charArray.length; i++) {
|
for (int i = 0; i < charArray.length; i++) {
|
||||||
charArray[i] = '\0';
|
charArray[i] = '\0';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void flush( byte[] charArray) {
|
public static void flush(byte[] charArray) {
|
||||||
if (charArray == null) return;
|
if (charArray == null) return;
|
||||||
for (int i = 0; i < charArray.length; i++) {
|
for (int i = 0; i < charArray.length; i++) {
|
||||||
charArray[i] = 0;
|
charArray[i] = 0;
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
|
|
||||||
package kellinwood.security.zipsigner.optional;
|
package kellinwood.security.zipsigner.optional;
|
||||||
|
|
||||||
import kellinwood.security.zipsigner.KeySet;
|
import kellinwood.security.zipsigner.KeySet;
|
||||||
import org.bouncycastle.cert.jcajce.JcaCertStore;
|
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.cms.jcajce.JcaSignerInfoGeneratorBuilder;
|
||||||
import org.bouncycastle.operator.ContentSigner;
|
import org.bouncycastle.operator.ContentSigner;
|
||||||
import org.bouncycastle.operator.DigestCalculatorProvider;
|
import org.bouncycastle.operator.DigestCalculatorProvider;
|
||||||
@ -39,11 +44,11 @@ public class SignatureBlockGenerator {
|
|||||||
JcaDigestCalculatorProviderBuilder jcaDigestCalculatorProviderBuilder = new JcaDigestCalculatorProviderBuilder().setProvider("SC");
|
JcaDigestCalculatorProviderBuilder jcaDigestCalculatorProviderBuilder = new JcaDigestCalculatorProviderBuilder().setProvider("SC");
|
||||||
DigestCalculatorProvider digestCalculatorProvider = jcaDigestCalculatorProviderBuilder.build();
|
DigestCalculatorProvider digestCalculatorProvider = jcaDigestCalculatorProviderBuilder.build();
|
||||||
|
|
||||||
JcaSignerInfoGeneratorBuilder jcaSignerInfoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder( digestCalculatorProvider);
|
JcaSignerInfoGeneratorBuilder jcaSignerInfoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder(digestCalculatorProvider);
|
||||||
jcaSignerInfoGeneratorBuilder.setDirectSignature(true);
|
jcaSignerInfoGeneratorBuilder.setDirectSignature(true);
|
||||||
SignerInfoGenerator signerInfoGenerator = jcaSignerInfoGeneratorBuilder.build(sha1Signer, keySet.getPublicKey());
|
SignerInfoGenerator signerInfoGenerator = jcaSignerInfoGeneratorBuilder.build(sha1Signer, keySet.getPublicKey());
|
||||||
|
|
||||||
gen.addSignerInfoGenerator( signerInfoGenerator);
|
gen.addSignerInfoGenerator(signerInfoGenerator);
|
||||||
|
|
||||||
gen.addCertificates(certs);
|
gen.addCertificates(certs);
|
||||||
|
|
||||||
|
@ -13,15 +13,15 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package kellinwood.zipio;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
package kellinwood.zipio;
|
||||||
|
|
||||||
import kellinwood.logging.LoggerInterface;
|
import kellinwood.logging.LoggerInterface;
|
||||||
import kellinwood.logging.LoggerManager;
|
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 int signature = 0x06054b50; // end of central dir signature 4 bytes
|
||||||
public short numberThisDisk = 0; // number of this disk 2 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
|
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;
|
private static LoggerInterface log;
|
||||||
|
|
||||||
public static CentralEnd read(ZipInput input) throws IOException
|
public static CentralEnd read(ZipInput input) throws IOException {
|
||||||
{
|
|
||||||
|
|
||||||
int signature = input.readInt();
|
int signature = input.readInt();
|
||||||
if (signature != 0x06054b50) {
|
if (signature != 0x06054b50) {
|
||||||
// back up to the signature
|
// back up to the signature
|
||||||
input.seek( input.getFilePointer() - 4);
|
input.seek(input.getFilePointer() - 4);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
CentralEnd entry = new CentralEnd();
|
CentralEnd entry = new CentralEnd();
|
||||||
|
|
||||||
entry.doRead( input);
|
entry.doRead(input);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoggerInterface getLogger() {
|
public static LoggerInterface getLogger() {
|
||||||
if (log == null) log = LoggerManager.getLogger( CentralEnd.class.getName());
|
if (log == null) log = LoggerManager.getLogger(CentralEnd.class.getName());
|
||||||
return log;
|
return log;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void doRead( ZipInput input) throws IOException
|
private void doRead(ZipInput input) throws IOException {
|
||||||
{
|
|
||||||
|
|
||||||
boolean debug = getLogger().isDebugEnabled();
|
boolean debug = getLogger().isDebugEnabled();
|
||||||
|
|
||||||
numberThisDisk = input.readShort();
|
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();
|
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();
|
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();
|
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();
|
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();
|
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();
|
short zipFileCommentLen = input.readShort();
|
||||||
fileComment = input.readString(zipFileCommentLen);
|
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();
|
boolean debug = getLogger().isDebugEnabled();
|
||||||
|
|
||||||
output.writeInt( signature);
|
output.writeInt(signature);
|
||||||
output.writeShort( numberThisDisk);
|
output.writeShort(numberThisDisk);
|
||||||
output.writeShort( centralStartDisk);
|
output.writeShort(centralStartDisk);
|
||||||
output.writeShort( numCentralEntries);
|
output.writeShort(numCentralEntries);
|
||||||
output.writeShort( totalCentralEntries);
|
output.writeShort(totalCentralEntries);
|
||||||
output.writeInt( centralDirectorySize );
|
output.writeInt(centralDirectorySize);
|
||||||
output.writeInt( centralStartOffset );
|
output.writeInt(centralStartOffset);
|
||||||
output.writeShort( (short)fileComment.length());
|
output.writeShort((short) fileComment.length());
|
||||||
output.writeString( fileComment);
|
output.writeString(fileComment);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,13 +13,17 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kellinwood.zipio;
|
package kellinwood.zipio;
|
||||||
|
|
||||||
|
import kellinwood.logging.LoggerInterface;
|
||||||
|
import kellinwood.logging.LoggerManager;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.File;
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.SequenceInputStream;
|
import java.io.SequenceInputStream;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -27,9 +31,6 @@ import java.util.zip.CRC32;
|
|||||||
import java.util.zip.Inflater;
|
import java.util.zip.Inflater;
|
||||||
import java.util.zip.InflaterInputStream;
|
import java.util.zip.InflaterInputStream;
|
||||||
|
|
||||||
import kellinwood.logging.LoggerInterface;
|
|
||||||
import kellinwood.logging.LoggerManager;
|
|
||||||
|
|
||||||
public class ZioEntry implements Cloneable {
|
public class ZioEntry implements Cloneable {
|
||||||
|
|
||||||
private ZipInput zipInput;
|
private ZipInput zipInput;
|
||||||
@ -62,36 +63,35 @@ public class ZioEntry implements Cloneable {
|
|||||||
|
|
||||||
private static LoggerInterface log;
|
private static LoggerInterface log;
|
||||||
|
|
||||||
public ZioEntry( ZipInput input) {
|
public ZioEntry(ZipInput input) {
|
||||||
zipInput = input;
|
zipInput = input;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoggerInterface getLogger() {
|
public static LoggerInterface getLogger() {
|
||||||
if (log == null) log = LoggerManager.getLogger( ZioEntry.class.getName());
|
if (log == null) log = LoggerManager.getLogger(ZioEntry.class.getName());
|
||||||
return log;
|
return log;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ZioEntry( String name) {
|
public ZioEntry(String name) {
|
||||||
filename = name;
|
filename = name;
|
||||||
fileComment = "";
|
fileComment = "";
|
||||||
compression = 8;
|
compression = 8;
|
||||||
extraData = new byte[0];
|
extraData = new byte[0];
|
||||||
setTime( System.currentTimeMillis());
|
setTime(System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ZioEntry( String name, String sourceDataFile)
|
public ZioEntry(String name, String sourceDataFile)
|
||||||
throws IOException
|
throws IOException {
|
||||||
{
|
zipInput = new ZipInput(sourceDataFile);
|
||||||
zipInput = new ZipInput( sourceDataFile);
|
|
||||||
filename = name;
|
filename = name;
|
||||||
fileComment = "";
|
fileComment = "";
|
||||||
this.compression = 0;
|
this.compression = 0;
|
||||||
this.size = (int)zipInput.getFileLength();
|
this.size = (int) zipInput.getFileLength();
|
||||||
this.compressedSize = this.size;
|
this.compressedSize = this.size;
|
||||||
|
|
||||||
if (getLogger().isDebugEnabled())
|
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
|
// compute CRC
|
||||||
CRC32 crc = new CRC32();
|
CRC32 crc = new CRC32();
|
||||||
@ -100,27 +100,25 @@ public class ZioEntry implements Cloneable {
|
|||||||
|
|
||||||
int numRead = 0;
|
int numRead = 0;
|
||||||
while (numRead != size) {
|
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) {
|
if (count > 0) {
|
||||||
crc.update( buffer, 0, count);
|
crc.update(buffer, 0, count);
|
||||||
numRead += count;
|
numRead += count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.crc32 = (int)crc.getValue();
|
this.crc32 = (int) crc.getValue();
|
||||||
|
|
||||||
zipInput.seek(0);
|
zipInput.seek(0);
|
||||||
this.dataPosition = 0;
|
this.dataPosition = 0;
|
||||||
extraData = new byte[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)
|
||||||
public ZioEntry( String name, String sourceDataFile, short compression, int crc32, int compressedSize, int size)
|
throws IOException {
|
||||||
throws IOException
|
zipInput = new ZipInput(sourceDataFile);
|
||||||
{
|
|
||||||
zipInput = new ZipInput( sourceDataFile);
|
|
||||||
filename = name;
|
filename = name;
|
||||||
fileComment = "";
|
fileComment = "";
|
||||||
this.compression = compression;
|
this.compression = compression;
|
||||||
@ -129,39 +127,35 @@ public class ZioEntry implements Cloneable {
|
|||||||
this.size = size;
|
this.size = size;
|
||||||
this.dataPosition = 0;
|
this.dataPosition = 0;
|
||||||
extraData = new byte[0];
|
extraData = new byte[0];
|
||||||
setTime( new File(sourceDataFile).lastModified());
|
setTime(new File(sourceDataFile).lastModified());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a copy with a new name
|
// Return a copy with a new name
|
||||||
public ZioEntry getClonedEntry( String newName)
|
public ZioEntry getClonedEntry(String newName) {
|
||||||
{
|
|
||||||
|
|
||||||
ZioEntry clone;
|
ZioEntry clone;
|
||||||
try {
|
try {
|
||||||
clone = (ZioEntry)this.clone();
|
clone = (ZioEntry) this.clone();
|
||||||
}
|
} catch (CloneNotSupportedException e) {
|
||||||
catch (CloneNotSupportedException e)
|
|
||||||
{
|
|
||||||
throw new IllegalStateException("clone() failed!");
|
throw new IllegalStateException("clone() failed!");
|
||||||
}
|
}
|
||||||
clone.setName(newName);
|
clone.setName(newName);
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void readLocalHeader() throws IOException
|
public void readLocalHeader() throws IOException {
|
||||||
{
|
|
||||||
ZipInput input = zipInput;
|
ZipInput input = zipInput;
|
||||||
int tmp;
|
int tmp;
|
||||||
boolean debug = getLogger().isDebugEnabled();
|
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
|
// 0 4 Local file header signature = 0x04034b50
|
||||||
int signature = input.readInt();
|
int signature = input.readInt();
|
||||||
if (signature != 0x04034b50) {
|
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
|
// This method is usually called just before the data read, so
|
||||||
@ -174,36 +168,44 @@ public class ZioEntry implements Cloneable {
|
|||||||
short tmpShort;
|
short tmpShort;
|
||||||
|
|
||||||
// 4 2 Version needed to extract (minimum)
|
// 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*/));
|
if (debug) log.debug(String.format("Version required: 0x%04x", tmpShort /*versionRequired*/));
|
||||||
|
|
||||||
// 6 2 General purpose bit flag
|
// 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 */));
|
if (debug) log.debug(String.format("General purpose bits: 0x%04x", tmpShort /* generalPurposeBits */));
|
||||||
|
|
||||||
// 8 2 Compression method
|
// 8 2 Compression method
|
||||||
/* compression */ tmpShort = input.readShort();
|
/* compression */
|
||||||
|
tmpShort = input.readShort();
|
||||||
if (debug) log.debug(String.format("Compression: 0x%04x", tmpShort /* compression */));
|
if (debug) log.debug(String.format("Compression: 0x%04x", tmpShort /* compression */));
|
||||||
|
|
||||||
// 10 2 File last modification time
|
// 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 */));
|
if (debug) log.debug(String.format("Modification time: 0x%04x", tmpShort /* modificationTime */));
|
||||||
|
|
||||||
// 12 2 File last modification date
|
// 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 */));
|
if (debug) log.debug(String.format("Modification date: 0x%04x", tmpShort /* modificationDate */));
|
||||||
|
|
||||||
// 14 4 CRC-32
|
// 14 4 CRC-32
|
||||||
/* crc32 */ tmpInt = input.readInt();
|
/* crc32 */
|
||||||
|
tmpInt = input.readInt();
|
||||||
if (debug) log.debug(String.format("CRC-32: 0x%04x", tmpInt /*crc32*/));
|
if (debug) log.debug(String.format("CRC-32: 0x%04x", tmpInt /*crc32*/));
|
||||||
|
|
||||||
// 18 4 Compressed size
|
// 18 4 Compressed size
|
||||||
/* compressedSize*/ tmpInt = input.readInt();
|
/* compressedSize*/
|
||||||
|
tmpInt = input.readInt();
|
||||||
if (debug) log.debug(String.format("Compressed size: 0x%04x", tmpInt /*compressedSize*/));
|
if (debug) log.debug(String.format("Compressed size: 0x%04x", tmpInt /*compressedSize*/));
|
||||||
|
|
||||||
// 22 4 Uncompressed size
|
// 22 4 Uncompressed size
|
||||||
/* size */ tmpInt = input.readInt();
|
/* size */
|
||||||
if (debug) log.debug(String.format("Size: 0x%04x", tmpInt /*size*/ ));
|
tmpInt = input.readInt();
|
||||||
|
if (debug) log.debug(String.format("Size: 0x%04x", tmpInt /*size*/));
|
||||||
|
|
||||||
// 26 2 File name length (n)
|
// 26 2 File name length (n)
|
||||||
short fileNameLen = input.readShort();
|
short fileNameLen = input.readShort();
|
||||||
@ -218,46 +220,45 @@ public class ZioEntry implements Cloneable {
|
|||||||
if (debug) log.debug("Filename: " + filename);
|
if (debug) log.debug("Filename: " + filename);
|
||||||
|
|
||||||
// Extra data
|
// Extra data
|
||||||
byte[] extra = input.readBytes( extraLen);
|
byte[] extra = input.readBytes(extraLen);
|
||||||
|
|
||||||
// Record the file position of this entry's data.
|
// Record the file position of this entry's data.
|
||||||
dataPosition = input.getFilePointer();
|
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) {
|
if (data == null && dataPosition < 0 && zipInput != null) {
|
||||||
readLocalHeader();
|
readLocalHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
localHeaderOffset = (int)output.getFilePointer();
|
localHeaderOffset = (int) output.getFilePointer();
|
||||||
|
|
||||||
boolean debug = getLogger().isDebugEnabled();
|
boolean debug = getLogger().isDebugEnabled();
|
||||||
|
|
||||||
if (debug) {
|
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) {
|
if (entryOut != null) {
|
||||||
entryOut.close();
|
entryOut.close();
|
||||||
size = entryOut.getSize();
|
size = entryOut.getSize();
|
||||||
data = ((ByteArrayOutputStream)entryOut.getWrappedStream()).toByteArray();
|
data = ((ByteArrayOutputStream) entryOut.getWrappedStream()).toByteArray();
|
||||||
compressedSize = data.length;
|
compressedSize = data.length;
|
||||||
crc32 = entryOut.getCRC();
|
crc32 = entryOut.getCRC();
|
||||||
}
|
}
|
||||||
|
|
||||||
output.writeInt( 0x04034b50);
|
output.writeInt(0x04034b50);
|
||||||
output.writeShort( versionRequired);
|
output.writeShort(versionRequired);
|
||||||
output.writeShort( generalPurposeBits);
|
output.writeShort(generalPurposeBits);
|
||||||
output.writeShort( compression);
|
output.writeShort(compression);
|
||||||
output.writeShort( modificationTime);
|
output.writeShort(modificationTime);
|
||||||
output.writeShort( modificationDate);
|
output.writeShort(modificationDate);
|
||||||
output.writeInt( crc32);
|
output.writeInt(crc32);
|
||||||
output.writeInt( compressedSize);
|
output.writeInt(compressedSize);
|
||||||
output.writeInt( size);
|
output.writeInt(size);
|
||||||
output.writeShort( (short)filename.length());
|
output.writeShort((short) filename.length());
|
||||||
|
|
||||||
numAlignBytes = 0;
|
numAlignBytes = 0;
|
||||||
|
|
||||||
@ -269,73 +270,70 @@ public class ZioEntry implements Cloneable {
|
|||||||
filename.length() + // plus filename
|
filename.length() + // plus filename
|
||||||
extraData.length; // plus extra data
|
extraData.length; // plus extra data
|
||||||
|
|
||||||
short dataPosMod4 = (short)(dataPos % 4);
|
short dataPosMod4 = (short) (dataPos % 4);
|
||||||
|
|
||||||
if (dataPosMod4 > 0) {
|
if (dataPosMod4 > 0) {
|
||||||
numAlignBytes = (short)(4 - dataPosMod4);
|
numAlignBytes = (short) (4 - dataPosMod4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 28 2 Extra field length (m)
|
// 28 2 Extra field length (m)
|
||||||
output.writeShort( (short)(extraData.length + numAlignBytes));
|
output.writeShort((short) (extraData.length + numAlignBytes));
|
||||||
|
|
||||||
// 30 n File name
|
// 30 n File name
|
||||||
output.writeString( filename);
|
output.writeString(filename);
|
||||||
|
|
||||||
// Extra data
|
// Extra data
|
||||||
output.writeBytes( extraData);
|
output.writeBytes(extraData);
|
||||||
|
|
||||||
// Zipalign bytes
|
// Zipalign bytes
|
||||||
if (numAlignBytes > 0) {
|
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 (debug) getLogger().debug(String.format("Data position 0x%08x", output.getFilePointer()));
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
output.writeBytes( data);
|
output.writeBytes(data);
|
||||||
if (debug) getLogger().debug(String.format("Wrote %d bytes", data.length));
|
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));
|
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];
|
byte[] buffer = new byte[bufferSize];
|
||||||
long totalCount = 0;
|
long totalCount = 0;
|
||||||
|
|
||||||
while (totalCount != compressedSize) {
|
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) {
|
if (numRead > 0) {
|
||||||
output.writeBytes(buffer, 0, numRead);
|
output.writeBytes(buffer, 0, numRead);
|
||||||
if (debug) getLogger().debug(String.format("Wrote %d bytes", numRead));
|
if (debug) getLogger().debug(String.format("Wrote %d bytes", numRead));
|
||||||
totalCount += numRead;
|
totalCount += numRead;
|
||||||
}
|
} else
|
||||||
else throw new IllegalStateException(String.format("EOF reached while copying %s with %d bytes left to go", filename, compressedSize - totalCount));
|
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
|
// 0 4 Central directory header signature = 0x02014b50
|
||||||
int signature = input.readInt();
|
int signature = input.readInt();
|
||||||
if (signature != 0x02014b50) {
|
if (signature != 0x02014b50) {
|
||||||
// back up to the signature
|
// back up to the signature
|
||||||
input.seek( input.getFilePointer() - 4);
|
input.seek(input.getFilePointer() - 4);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ZioEntry entry = new ZioEntry( input);
|
ZioEntry entry = new ZioEntry(input);
|
||||||
|
|
||||||
entry.doRead( input);
|
entry.doRead(input);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doRead( ZipInput input) throws IOException
|
private void doRead(ZipInput input) throws IOException {
|
||||||
{
|
|
||||||
|
|
||||||
boolean debug = getLogger().isDebugEnabled();
|
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));
|
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.
|
// 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) {
|
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
|
// 8 2 Compression method
|
||||||
@ -406,12 +404,12 @@ public class ZioEntry implements Cloneable {
|
|||||||
filename = input.readString(fileNameLen);
|
filename = input.readString(fileNameLen);
|
||||||
if (debug) log.debug("Filename: " + filename);
|
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);
|
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.
|
// Don't write zero-length entries with compression.
|
||||||
if (size == 0) {
|
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;
|
if (data != null) return data;
|
||||||
|
|
||||||
byte[] tmpdata = new byte[size];
|
byte[] tmpdata = new byte[size];
|
||||||
@ -433,8 +432,9 @@ public class ZioEntry implements Cloneable {
|
|||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
while (count != size) {
|
while (count != size) {
|
||||||
int numRead = din.read( tmpdata, count, 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));
|
if (numRead < 0)
|
||||||
|
throw new IllegalStateException(String.format("Read failed, expecting %d bytes, got %d instead", size, count));
|
||||||
count += numRead;
|
count += numRead;
|
||||||
}
|
}
|
||||||
return tmpdata;
|
return tmpdata;
|
||||||
@ -451,66 +451,64 @@ public class ZioEntry implements Cloneable {
|
|||||||
if (entryOut != null) {
|
if (entryOut != null) {
|
||||||
entryOut.close();
|
entryOut.close();
|
||||||
size = entryOut.getSize();
|
size = entryOut.getSize();
|
||||||
data = ((ByteArrayOutputStream)entryOut.getWrappedStream()).toByteArray();
|
data = ((ByteArrayOutputStream) entryOut.getWrappedStream()).toByteArray();
|
||||||
compressedSize = data.length;
|
compressedSize = data.length;
|
||||||
crc32 = entryOut.getCRC();
|
crc32 = entryOut.getCRC();
|
||||||
entryOut = null;
|
entryOut = null;
|
||||||
InputStream rawis = new ByteArrayInputStream( data);
|
InputStream rawis = new ByteArrayInputStream(data);
|
||||||
if (compression == 0) return rawis;
|
if (compression == 0) return rawis;
|
||||||
else {
|
else {
|
||||||
// Hacky, inflate using a sequence of input streams that returns 1 byte more than the actual length of the data.
|
// 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).
|
// 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;
|
ZioEntryInputStream dataStream;
|
||||||
dataStream = new ZioEntryInputStream(this);
|
dataStream = new ZioEntryInputStream(this);
|
||||||
if (monitorStream != null) dataStream.setMonitorStream( monitorStream);
|
if (monitorStream != null) dataStream.setMonitorStream(monitorStream);
|
||||||
if (compression != 0) {
|
if (compression != 0) {
|
||||||
// Note: When using nowrap=true with Inflater it is also necessary to provide
|
// 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
|
// an extra "dummy" byte as input. This is required by the ZLIB native library
|
||||||
// in order to support certain optimizations.
|
// in order to support certain optimizations.
|
||||||
dataStream.setReturnDummyByte(true);
|
dataStream.setReturnDummyByte(true);
|
||||||
return new InflaterInputStream( dataStream, new Inflater( true));
|
return new InflaterInputStream(dataStream, new Inflater(true));
|
||||||
}
|
} else return dataStream;
|
||||||
else return dataStream;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns an output stream for writing an entry's data.
|
// Returns an output stream for writing an entry's data.
|
||||||
public OutputStream getOutputStream()
|
public OutputStream getOutputStream() {
|
||||||
{
|
entryOut = new ZioEntryOutputStream(compression, new ByteArrayOutputStream());
|
||||||
entryOut = new ZioEntryOutputStream( compression, new ByteArrayOutputStream());
|
|
||||||
return entryOut;
|
return entryOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void write( ZipOutput output) throws IOException {
|
public void write(ZipOutput output) throws IOException {
|
||||||
boolean debug = getLogger().isDebugEnabled();
|
boolean debug = getLogger().isDebugEnabled();
|
||||||
|
|
||||||
|
|
||||||
output.writeInt( 0x02014b50);
|
output.writeInt(0x02014b50);
|
||||||
output.writeShort( versionMadeBy);
|
output.writeShort(versionMadeBy);
|
||||||
output.writeShort( versionRequired);
|
output.writeShort(versionRequired);
|
||||||
output.writeShort( generalPurposeBits);
|
output.writeShort(generalPurposeBits);
|
||||||
output.writeShort( compression);
|
output.writeShort(compression);
|
||||||
output.writeShort( modificationTime);
|
output.writeShort(modificationTime);
|
||||||
output.writeShort( modificationDate);
|
output.writeShort(modificationDate);
|
||||||
output.writeInt( crc32);
|
output.writeInt(crc32);
|
||||||
output.writeInt( compressedSize);
|
output.writeInt(compressedSize);
|
||||||
output.writeInt( size);
|
output.writeInt(size);
|
||||||
output.writeShort( (short)filename.length());
|
output.writeShort((short) filename.length());
|
||||||
output.writeShort( (short)(extraData.length + numAlignBytes));
|
output.writeShort((short) (extraData.length + numAlignBytes));
|
||||||
output.writeShort( (short)fileComment.length());
|
output.writeShort((short) fileComment.length());
|
||||||
output.writeShort( diskNumberStart);
|
output.writeShort(diskNumberStart);
|
||||||
output.writeShort( internalAttributes);
|
output.writeShort(internalAttributes);
|
||||||
output.writeInt( externalAttributes);
|
output.writeInt(externalAttributes);
|
||||||
output.writeInt( localHeaderOffset);
|
output.writeInt(localHeaderOffset);
|
||||||
|
|
||||||
output.writeString( filename);
|
output.writeString(filename);
|
||||||
output.writeBytes( extraData);
|
output.writeBytes(extraData);
|
||||||
if (numAlignBytes > 0) output.writeBytes( alignBytes, 0, numAlignBytes);
|
if (numAlignBytes > 0) output.writeBytes(alignBytes, 0, numAlignBytes);
|
||||||
output.writeString( fileComment);
|
output.writeString(fileComment);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -518,13 +516,13 @@ public class ZioEntry implements Cloneable {
|
|||||||
* Returns timetamp in Java format
|
* Returns timetamp in Java format
|
||||||
*/
|
*/
|
||||||
public long getTime() {
|
public long getTime() {
|
||||||
int year = (int)(((modificationDate >> 9) & 0x007f) + 80);
|
int year = (int) (((modificationDate >> 9) & 0x007f) + 80);
|
||||||
int month = (int)(((modificationDate >> 5) & 0x000f) - 1);
|
int month = (int) (((modificationDate >> 5) & 0x000f) - 1);
|
||||||
int day = (int)(modificationDate & 0x001f);
|
int day = (int) (modificationDate & 0x001f);
|
||||||
int hour = (int)((modificationTime >> 11) & 0x001f);
|
int hour = (int) ((modificationTime >> 11) & 0x001f);
|
||||||
int minute = (int)((modificationTime >> 5) & 0x003f);
|
int minute = (int) ((modificationTime >> 5) & 0x003f);
|
||||||
int seconds = (int)((modificationTime << 1) & 0x003e);
|
int seconds = (int) ((modificationTime << 1) & 0x003e);
|
||||||
Date d = new Date( year, month, day, hour, minute, seconds);
|
Date d = new Date(year, month, day, hour, minute, seconds);
|
||||||
return d.getTime();
|
return d.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -537,15 +535,14 @@ public class ZioEntry implements Cloneable {
|
|||||||
int year = d.getYear() + 1900;
|
int year = d.getYear() + 1900;
|
||||||
if (year < 1980) {
|
if (year < 1980) {
|
||||||
dtime = (1 << 21) | (1 << 16);
|
dtime = (1 << 21) | (1 << 16);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
dtime = (year - 1980) << 25 | (d.getMonth() + 1) << 21 |
|
dtime = (year - 1980) << 25 | (d.getMonth() + 1) << 21 |
|
||||||
d.getDate() << 16 | d.getHours() << 11 | d.getMinutes() << 5 |
|
d.getDate() << 16 | d.getHours() << 11 | d.getMinutes() << 5 |
|
||||||
d.getSeconds() >> 1;
|
d.getSeconds() >> 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
modificationDate = (short)(dtime >> 16);
|
modificationDate = (short) (dtime >> 16);
|
||||||
modificationTime = (short)(dtime & 0xFFFF);
|
modificationTime = (short) (dtime & 0xFFFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDirectory() {
|
public boolean isDirectory() {
|
||||||
@ -556,13 +553,15 @@ public class ZioEntry implements Cloneable {
|
|||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setName( String filename) {
|
public void setName(String filename) {
|
||||||
this.filename = filename;
|
this.filename = filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Use 0 (STORED), or 8 (DEFLATE). */
|
/**
|
||||||
public void setCompression( int compression) {
|
* Use 0 (STORED), or 8 (DEFLATE).
|
||||||
this.compression = (short)compression;
|
*/
|
||||||
|
public void setCompression(int compression) {
|
||||||
|
this.compression = (short) compression;
|
||||||
}
|
}
|
||||||
|
|
||||||
public short getVersionMadeBy() {
|
public short getVersionMadeBy() {
|
||||||
|
@ -13,18 +13,21 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kellinwood.zipio;
|
package kellinwood.zipio;
|
||||||
|
|
||||||
|
import kellinwood.logging.LoggerInterface;
|
||||||
|
import kellinwood.logging.LoggerManager;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.RandomAccessFile;
|
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 {
|
public class ZioEntryInputStream extends InputStream {
|
||||||
|
|
||||||
RandomAccessFile raf;
|
RandomAccessFile raf;
|
||||||
@ -35,9 +38,9 @@ public class ZioEntryInputStream extends InputStream {
|
|||||||
boolean returnDummyByte = false;
|
boolean returnDummyByte = false;
|
||||||
OutputStream monitor = null;
|
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();
|
debug = log.isDebugEnabled();
|
||||||
offset = 0;
|
offset = 0;
|
||||||
size = entry.getCompressedSize();
|
size = entry.getCompressedSize();
|
||||||
@ -45,9 +48,8 @@ public class ZioEntryInputStream extends InputStream {
|
|||||||
long dpos = entry.getDataPosition();
|
long dpos = entry.getDataPosition();
|
||||||
if (dpos >= 0) {
|
if (dpos >= 0) {
|
||||||
if (debug) log.debug(String.format("Seeking to %d", entry.getDataPosition()));
|
if (debug) log.debug(String.format("Seeking to %d", entry.getDataPosition()));
|
||||||
raf.seek( entry.getDataPosition());
|
raf.seek(entry.getDataPosition());
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// seeks to, then reads, the local header, causing the
|
// seeks to, then reads, the local header, causing the
|
||||||
// file pointer to be positioned at the start of the data.
|
// file pointer to be positioned at the start of the data.
|
||||||
entry.readLocalHeader();
|
entry.readLocalHeader();
|
||||||
@ -55,7 +57,7 @@ public class ZioEntryInputStream extends InputStream {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setReturnDummyByte( boolean returnExtraByte) {
|
public void setReturnDummyByte(boolean returnExtraByte) {
|
||||||
returnDummyByte = returnExtraByte;
|
returnDummyByte = returnExtraByte;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,22 +89,20 @@ public class ZioEntryInputStream extends InputStream {
|
|||||||
if (returnDummyByte) {
|
if (returnDummyByte) {
|
||||||
returnDummyByte = false;
|
returnDummyByte = false;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
} else return -1;
|
||||||
else return -1;
|
|
||||||
}
|
}
|
||||||
int b = raf.read();
|
int b = raf.read();
|
||||||
if (b >= 0) {
|
if (b >= 0) {
|
||||||
if (monitor != null) monitor.write(b);
|
if (monitor != null) monitor.write(b);
|
||||||
if (debug) log.debug("Read 1 byte");
|
if (debug) log.debug("Read 1 byte");
|
||||||
offset += 1;
|
offset += 1;
|
||||||
}
|
} else if (debug) log.debug("Read 0 bytes");
|
||||||
else if (debug) log.debug("Read 0 bytes");
|
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(byte[] b, int off, int len) throws IOException {
|
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 {
|
private int readBytes(byte[] b, int off, int len) throws IOException {
|
||||||
@ -111,10 +111,9 @@ public class ZioEntryInputStream extends InputStream {
|
|||||||
returnDummyByte = false;
|
returnDummyByte = false;
|
||||||
b[off] = 0;
|
b[off] = 0;
|
||||||
return 1;
|
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);
|
int numRead = raf.read(b, off, numToRead);
|
||||||
if (numRead > 0) {
|
if (numRead > 0) {
|
||||||
if (monitor != null) monitor.write(b, off, numRead);
|
if (monitor != null) monitor.write(b, off, numRead);
|
||||||
@ -126,13 +125,13 @@ public class ZioEntryInputStream extends InputStream {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(byte[] b) throws IOException {
|
public int read(byte[] b) throws IOException {
|
||||||
return readBytes( b, 0, b.length);
|
return readBytes(b, 0, b.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long skip(long n) throws IOException {
|
public long skip(long n) throws IOException {
|
||||||
long numToSkip = Math.min( n, available());
|
long numToSkip = Math.min(n, available());
|
||||||
raf.seek( raf.getFilePointer() + numToSkip);
|
raf.seek(raf.getFilePointer() + numToSkip);
|
||||||
if (debug) log.debug(String.format("Skipped %d bytes", numToSkip));
|
if (debug) log.debug(String.format("Skipped %d bytes", numToSkip));
|
||||||
return numToSkip;
|
return numToSkip;
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,9 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kellinwood.zipio;
|
package kellinwood.zipio;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.zip.CRC32;
|
import java.util.zip.CRC32;
|
||||||
@ -29,18 +29,17 @@ public class ZioEntryOutputStream extends OutputStream {
|
|||||||
OutputStream wrapped;
|
OutputStream wrapped;
|
||||||
OutputStream downstream;
|
OutputStream downstream;
|
||||||
|
|
||||||
public ZioEntryOutputStream( int compression, OutputStream wrapped)
|
public ZioEntryOutputStream(int compression, OutputStream wrapped) {
|
||||||
{
|
|
||||||
this.wrapped = wrapped;
|
this.wrapped = wrapped;
|
||||||
if (compression != 0)
|
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;
|
else downstream = wrapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
downstream.flush();
|
downstream.flush();
|
||||||
downstream.close();
|
downstream.close();
|
||||||
crcValue = (int)crc.getValue();
|
crcValue = (int) crc.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCRC() {
|
public int getCRC() {
|
||||||
@ -58,14 +57,14 @@ public class ZioEntryOutputStream extends OutputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void write(byte[] b, int off, int len) throws IOException {
|
public void write(byte[] b, int off, int len) throws IOException {
|
||||||
downstream.write( b, off, len);
|
downstream.write(b, off, len);
|
||||||
crc.update( b, off, len);
|
crc.update(b, off, len);
|
||||||
size += len;
|
size += len;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write(int b) throws IOException {
|
public void write(int b) throws IOException {
|
||||||
downstream.write( b);
|
downstream.write(b);
|
||||||
crc.update( b);
|
crc.update(b);
|
||||||
size += 1;
|
size += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,8 +72,7 @@ public class ZioEntryOutputStream extends OutputStream {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OutputStream getWrappedStream()
|
public OutputStream getWrappedStream() {
|
||||||
{
|
|
||||||
return wrapped;
|
return wrapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,16 +13,17 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kellinwood.zipio;
|
package kellinwood.zipio;
|
||||||
|
|
||||||
|
import kellinwood.logging.LoggerInterface;
|
||||||
|
import kellinwood.logging.LoggerManager;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.RandomAccessFile;
|
import java.io.RandomAccessFile;
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -31,15 +32,10 @@ import java.util.jar.Manifest;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
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;
|
static LoggerInterface log;
|
||||||
|
|
||||||
public String inputFilename;
|
public String inputFilename;
|
||||||
@ -47,14 +43,13 @@ public class ZipInput implements Closeable
|
|||||||
long fileLength;
|
long fileLength;
|
||||||
int scanIterations = 0;
|
int scanIterations = 0;
|
||||||
|
|
||||||
Map<String,ZioEntry> zioEntries = new LinkedHashMap<String,ZioEntry>();
|
Map<String, ZioEntry> zioEntries = new LinkedHashMap<String, ZioEntry>();
|
||||||
CentralEnd centralEnd;
|
CentralEnd centralEnd;
|
||||||
Manifest manifest;
|
Manifest manifest;
|
||||||
|
|
||||||
public ZipInput( String filename) throws IOException
|
public ZipInput(String filename) throws IOException {
|
||||||
{
|
|
||||||
this.inputFilename = filename;
|
this.inputFilename = filename;
|
||||||
in = new RandomAccessFile( new File( inputFilename), "r");
|
in = new RandomAccessFile(new File(inputFilename), "r");
|
||||||
fileLength = in.length();
|
fileLength = in.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,32 +66,32 @@ public class ZipInput implements Closeable
|
|||||||
return fileLength;
|
return fileLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ZipInput read( String filename) throws IOException {
|
public static ZipInput read(String filename) throws IOException {
|
||||||
ZipInput zipInput = new ZipInput( filename);
|
ZipInput zipInput = new ZipInput(filename);
|
||||||
zipInput.doRead();
|
zipInput.doRead();
|
||||||
return zipInput;
|
return zipInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ZioEntry getEntry( String filename) {
|
public ZioEntry getEntry(String filename) {
|
||||||
return zioEntries.get(filename);
|
return zioEntries.get(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String,ZioEntry> getEntries() {
|
public Map<String, ZioEntry> getEntries() {
|
||||||
return zioEntries;
|
return zioEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the names of immediate children in the directory with the given name.
|
/**
|
||||||
|
* 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 "/"
|
* The path value must end with a "/" character. Use a value of "/"
|
||||||
* to get the root entries.
|
* 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.endsWith("/")) throw new IllegalArgumentException("Invalid path -- does not end with '/'");
|
||||||
|
|
||||||
if (path.startsWith("/")) path = path.substring(1);
|
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>();
|
Set<String> names = new TreeSet<String>();
|
||||||
|
|
||||||
@ -111,45 +106,46 @@ public class ZipInput implements Closeable
|
|||||||
if (manifest == null) {
|
if (manifest == null) {
|
||||||
ZioEntry e = zioEntries.get("META-INF/MANIFEST.MF");
|
ZioEntry e = zioEntries.get("META-INF/MANIFEST.MF");
|
||||||
if (e != null) {
|
if (e != null) {
|
||||||
manifest = new Manifest( e.getInputStream());
|
manifest = new Manifest(e.getInputStream());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return manifest;
|
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
|
* Scan the end of the file for the end of central directory record (EOCDR).
|
||||||
initial buffer size (e.g., 256).
|
* 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 {
|
public long scanForEOCDR(int size) throws IOException {
|
||||||
if (size > fileLength || size > 65536) throw new IllegalStateException( "End of central directory not found in " + inputFilename);
|
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];
|
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--) {
|
for (int i = scanSize - 22; i >= 0; i--) {
|
||||||
scanIterations += 1;
|
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 fileLength - scanSize + i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return scanForEOCDR( size * 2);
|
return scanForEOCDR(size * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void doRead()
|
private void doRead() {
|
||||||
{
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
long posEOCDR = scanForEOCDR( 256);
|
long posEOCDR = scanForEOCDR(256);
|
||||||
in.seek( posEOCDR);
|
in.seek(posEOCDR);
|
||||||
centralEnd = CentralEnd.read( this);
|
centralEnd = CentralEnd.read(this);
|
||||||
|
|
||||||
boolean debug = getLogger().isDebugEnabled();
|
boolean debug = getLogger().isDebugEnabled();
|
||||||
if (debug) {
|
if (debug) {
|
||||||
@ -157,33 +153,35 @@ public class ZipInput implements Closeable
|
|||||||
getLogger().debug(String.format("Directory entries=%d, size=%d, offset=%d/0x%08x", centralEnd.totalCentralEntries,
|
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++) {
|
for (int i = 0; i < centralEnd.totalCentralEntries; i++) {
|
||||||
ZioEntry entry = ZioEntry.read(this);
|
ZioEntry entry = ZioEntry.read(this);
|
||||||
zioEntries.put( entry.getName(), entry);
|
zioEntries.put(entry.getName(), entry);
|
||||||
if (debug) ZipListingHelper.listEntry( getLogger(), entry);
|
if (debug) ZipListingHelper.listEntry(getLogger(), entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} catch (Throwable t) {
|
||||||
catch (Throwable t) {
|
|
||||||
t.printStackTrace();
|
t.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
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 {
|
public long getFilePointer() throws IOException {
|
||||||
return in.getFilePointer();
|
return in.getFilePointer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void seek( long position) throws IOException {
|
public void seek(long position) throws IOException {
|
||||||
in.seek(position);
|
in.seek(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,7 +189,7 @@ public class ZipInput implements Closeable
|
|||||||
return in.readByte();
|
return in.readByte();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int readInt() throws IOException{
|
public int readInt() throws IOException {
|
||||||
int result = 0;
|
int result = 0;
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
result |= (in.readUnsignedByte() << (8 * i));
|
result |= (in.readUnsignedByte() << (8 * i));
|
||||||
@ -207,7 +205,7 @@ public class ZipInput implements Closeable
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String readString( int length) throws IOException {
|
public String readString(int length) throws IOException {
|
||||||
|
|
||||||
byte[] buffer = new byte[length];
|
byte[] buffer = new byte[length];
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
@ -216,7 +214,7 @@ public class ZipInput implements Closeable
|
|||||||
return new String(buffer);
|
return new String(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] readBytes( int length) throws IOException {
|
public byte[] readBytes(int length) throws IOException {
|
||||||
|
|
||||||
byte[] buffer = new byte[length];
|
byte[] buffer = new byte[length];
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
@ -225,8 +223,8 @@ public class ZipInput implements Closeable
|
|||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read( byte[] b, int offset, int length) throws IOException {
|
public int read(byte[] b, int offset, int length) throws IOException {
|
||||||
return in.read( b, offset, length);
|
return in.read(b, offset, length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,31 +13,29 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kellinwood.zipio;
|
package kellinwood.zipio;
|
||||||
|
|
||||||
import java.util.Date;
|
import kellinwood.logging.LoggerInterface;
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import kellinwood.logging.LoggerInterface;
|
import java.util.Date;
|
||||||
import kellinwood.logging.LoggerManager;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class ZipListingHelper
|
public class ZipListingHelper {
|
||||||
{
|
|
||||||
|
|
||||||
static DateFormat dateFormat = new SimpleDateFormat("MM-dd-yy HH:mm");
|
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(" Length Method Size Ratio Date Time CRC-32 Name");
|
||||||
log.debug("-------- ------ ------- ----- ---- ---- ------ ----");
|
log.debug("-------- ------ ------- ----- ---- ---- ------ ----");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void listEntry(LoggerInterface log, ZioEntry entry)
|
public static void listEntry(LoggerInterface log, ZioEntry entry) {
|
||||||
{
|
|
||||||
int ratio = 0;
|
int ratio = 0;
|
||||||
if (entry.getSize() > 0) ratio = (100 * (entry.getSize() - entry.getCompressedSize())) / entry.getSize();
|
if (entry.getSize() > 0) ratio = (100 * (entry.getSize() - entry.getCompressedSize())) / entry.getSize();
|
||||||
log.debug(String.format("%8d %6s %8d %4d%% %s %08x %s",
|
log.debug(String.format("%8d %6s %8d %4d%% %s %08x %s",
|
||||||
@ -45,7 +43,7 @@ public class ZipListingHelper
|
|||||||
entry.getCompression() == 0 ? "Stored" : "Defl:N",
|
entry.getCompression() == 0 ? "Stored" : "Defl:N",
|
||||||
entry.getCompressedSize(),
|
entry.getCompressedSize(),
|
||||||
ratio,
|
ratio,
|
||||||
dateFormat.format( new Date( entry.getTime())),
|
dateFormat.format(new Date(entry.getTime())),
|
||||||
entry.getCrc32(),
|
entry.getCrc32(),
|
||||||
entry.getName()));
|
entry.getName()));
|
||||||
}
|
}
|
||||||
|
@ -13,24 +13,25 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kellinwood.zipio;
|
package kellinwood.zipio;
|
||||||
|
|
||||||
|
import kellinwood.logging.LoggerInterface;
|
||||||
|
import kellinwood.logging.LoggerManager;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.List;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import kellinwood.logging.LoggerInterface;
|
|
||||||
import kellinwood.logging.LoggerManager;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class ZipOutput
|
public class ZipOutput {
|
||||||
{
|
|
||||||
|
|
||||||
static LoggerInterface log;
|
static LoggerInterface log;
|
||||||
|
|
||||||
@ -41,30 +42,26 @@ public class ZipOutput
|
|||||||
List<ZioEntry> entriesWritten = new LinkedList<ZioEntry>();
|
List<ZioEntry> entriesWritten = new LinkedList<ZioEntry>();
|
||||||
Set<String> namesWritten = new HashSet<String>();
|
Set<String> namesWritten = new HashSet<String>();
|
||||||
|
|
||||||
public ZipOutput( String filename) throws IOException
|
public ZipOutput(String filename) throws IOException {
|
||||||
{
|
|
||||||
this.outputFilename = filename;
|
this.outputFilename = filename;
|
||||||
File ofile = new File( outputFilename);
|
File ofile = new File(outputFilename);
|
||||||
init(ofile);
|
init(ofile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ZipOutput( File outputFile) throws IOException
|
public ZipOutput(File outputFile) throws IOException {
|
||||||
{
|
|
||||||
this.outputFilename = outputFile.getAbsolutePath();
|
this.outputFilename = outputFile.getAbsolutePath();
|
||||||
File ofile = outputFile;
|
File ofile = outputFile;
|
||||||
init(ofile);
|
init(ofile);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init( File ofile) throws IOException
|
private void init(File ofile) throws IOException {
|
||||||
{
|
|
||||||
if (ofile.exists()) ofile.delete();
|
if (ofile.exists()) ofile.delete();
|
||||||
out = new FileOutputStream( ofile);
|
out = new FileOutputStream(ofile);
|
||||||
if (getLogger().isDebugEnabled()) ZipListingHelper.listHeader( getLogger());
|
if (getLogger().isDebugEnabled()) ZipListingHelper.listHeader(getLogger());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ZipOutput( OutputStream os) throws IOException
|
public ZipOutput(OutputStream os) throws IOException {
|
||||||
{
|
|
||||||
out = os;
|
out = os;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,38 +70,39 @@ public class ZipOutput
|
|||||||
return log;
|
return log;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write( ZioEntry entry) throws IOException {
|
public void write(ZioEntry entry) throws IOException {
|
||||||
String entryName = entry.getName();
|
String entryName = entry.getName();
|
||||||
if (namesWritten.contains( entryName)) {
|
if (namesWritten.contains(entryName)) {
|
||||||
getLogger().warning("Skipping duplicate file in output: " + entryName);
|
getLogger().warning("Skipping duplicate file in output: " + entryName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
entry.writeLocalEntry( this);
|
entry.writeLocalEntry(this);
|
||||||
entriesWritten.add( entry);
|
entriesWritten.add(entry);
|
||||||
namesWritten.add( entryName);
|
namesWritten.add(entryName);
|
||||||
if (getLogger().isDebugEnabled()) ZipListingHelper.listEntry( getLogger(), entry);
|
if (getLogger().isDebugEnabled()) ZipListingHelper.listEntry(getLogger(), entry);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void close() throws IOException {
|
||||||
public void close() throws IOException
|
|
||||||
{
|
|
||||||
CentralEnd centralEnd = new CentralEnd();
|
CentralEnd centralEnd = new CentralEnd();
|
||||||
|
|
||||||
centralEnd.centralStartOffset = (int)getFilePointer();
|
centralEnd.centralStartOffset = (int) getFilePointer();
|
||||||
centralEnd.numCentralEntries = centralEnd.totalCentralEntries = (short)entriesWritten.size();
|
centralEnd.numCentralEntries = centralEnd.totalCentralEntries = (short) entriesWritten.size();
|
||||||
|
|
||||||
for (ZioEntry entry : entriesWritten) {
|
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.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 {
|
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];
|
byte[] data = new byte[4];
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
data[i] = (byte)(value & 0xFF);
|
data[i] = (byte) (value & 0xFF);
|
||||||
value = value >> 8;
|
value = value >> 8;
|
||||||
}
|
}
|
||||||
out.write( data);
|
out.write(data);
|
||||||
filePointer += 4;
|
filePointer += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeShort( short value) throws IOException {
|
public void writeShort(short value) throws IOException {
|
||||||
byte[] data = new byte[2];
|
byte[] data = new byte[2];
|
||||||
for (int i = 0; i < 2; i++) {
|
for (int i = 0; i < 2; i++) {
|
||||||
data[i] = (byte)(value & 0xFF);
|
data[i] = (byte) (value & 0xFF);
|
||||||
value = (short)(value >> 8);
|
value = (short) (value >> 8);
|
||||||
}
|
}
|
||||||
out.write( data);
|
out.write(data);
|
||||||
filePointer += 2;
|
filePointer += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeString( String value) throws IOException {
|
public void writeString(String value) throws IOException {
|
||||||
|
|
||||||
byte[] data = value.getBytes();
|
byte[] data = value.getBytes();
|
||||||
out.write( data);
|
out.write(data);
|
||||||
filePointer += data.length;
|
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;
|
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;
|
filePointer += length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user