blob: bed91be4be4e46722afc7653b64e3aaadc84af55 [file] [log] [blame]
package aQute.bnd.signing;
import java.io.*;
import java.security.*;
import java.security.KeyStore.*;
import java.util.*;
import java.util.jar.*;
import java.util.regex.*;
import aQute.lib.base64.*;
import aQute.lib.osgi.*;
/**
* This class is used with the aQute.lib.osgi package, it signs jars with DSA
* signature.
*
* -sign: md5, sha1
*/
public class Signer extends Processor {
static Pattern METAINFDIR = Pattern.compile("META-INF/[^/]*");
String digestNames[] = new String[] { "MD5" };
File keystoreFile = new File("keystore");
String password;
String alias;
public void signJar(Jar jar) {
if (digestNames == null || digestNames.length == 0)
error("Need at least one digest algorithm name, none are specified");
if (keystoreFile == null || !keystoreFile.getAbsoluteFile().exists()) {
error("No such keystore file: " + keystoreFile);
return;
}
if (alias == null) {
error("Private key alias not set for signing");
return;
}
MessageDigest digestAlgorithms[] = new MessageDigest[digestNames.length];
getAlgorithms(digestNames, digestAlgorithms);
try {
Manifest manifest = jar.getManifest();
manifest.getMainAttributes().putValue("Signed-By", "Bnd");
// Create a new manifest that contains the
// Name parts with the specified digests
ByteArrayOutputStream o = new ByteArrayOutputStream();
manifest.write(o);
doManifest(jar, digestNames, digestAlgorithms, o);
o.flush();
byte newManifestBytes[] = o.toByteArray();
jar.putResource("META-INF/MANIFEST.MF", new EmbeddedResource(
newManifestBytes, 0));
// Use the bytes from the new manifest to create
// a signature file
byte[] signatureFileBytes = doSignatureFile(digestNames,
digestAlgorithms, newManifestBytes);
jar.putResource("META-INF/BND.SF", new EmbeddedResource(
signatureFileBytes, 0));
// Now we must create an RSA signature
// this requires the private key from the keystore
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
KeyStore.PrivateKeyEntry privateKeyEntry = null;
try {
java.io.FileInputStream keystoreInputStream = new java.io.FileInputStream(
keystoreFile);
keystore.load(keystoreInputStream, password == null ? null
: password.toCharArray());
keystoreInputStream.close();
privateKeyEntry = (PrivateKeyEntry) keystore.getEntry(
alias, new KeyStore.PasswordProtection(password
.toCharArray()));
} catch (Exception e) {
error("No able to load the private key from the give keystore("+keystoreFile.getAbsolutePath()+") with alias "+alias+" : "
+ e);
return;
}
PrivateKey privateKey = privateKeyEntry.getPrivateKey();
Signature signature = Signature.getInstance("MD5withRSA");
signature.initSign(privateKey);
signature.update(signatureFileBytes);
signature.sign();
// TODO, place the SF in a PCKS#7 structure ...
// no standard class for this? The following
// is an idea but we will to have do ASN.1 BER
// encoding ...
ByteArrayOutputStream tmpStream = new ByteArrayOutputStream();
jar.putResource("META-INF/BND.RSA", new EmbeddedResource(tmpStream
.toByteArray(), 0));
} catch (Exception e) {
error("During signing: " + e);
}
}
private byte[] doSignatureFile(String[] digestNames,
MessageDigest[] algorithms, byte[] manbytes) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(out);
ps.print("Signature-Version: 1.0\r\n");
for (int a = 0; a < algorithms.length; a++) {
if (algorithms[a] != null) {
byte[] digest = algorithms[a].digest(manbytes);
ps.print(digestNames[a] + "-Digest-Manifest: ");
ps.print(new Base64(digest));
ps.print("\r\n");
}
}
return out.toByteArray();
}
private void doManifest(Jar jar, String[] digestNames,
MessageDigest[] algorithms, OutputStream out) throws Exception {
for (Map.Entry<String,Resource> entry : jar.getResources().entrySet()) {
String name = entry.getKey();
if (!METAINFDIR.matcher(name).matches()) {
out.write("\r\n".getBytes());
out.write("Name: ".getBytes());
out.write(name.getBytes());
out.write("\r\n".getBytes());
digest(algorithms, entry.getValue());
for (int a = 0; a < algorithms.length; a++) {
if (algorithms[a] != null) {
byte[] digest = algorithms[a].digest();
String header = digestNames[a] + "-Digest: "
+ new Base64(digest) + "\r\n";
out.write(header.getBytes());
}
}
}
}
}
private void digest(MessageDigest[] algorithms, Resource r)
throws Exception {
InputStream in = r.openInputStream();
byte[] data = new byte[1024];
int size = in.read(data);
while (size > 0) {
for (int a = 0; a < algorithms.length; a++) {
if (algorithms[a] != null) {
algorithms[a].update(data, 0, size);
}
}
size = in.read(data);
}
}
private void getAlgorithms(String[] digestNames, MessageDigest[] algorithms) {
for (int i = 0; i < algorithms.length; i++) {
String name = digestNames[i];
try {
algorithms[i] = MessageDigest.getInstance(name);
} catch (NoSuchAlgorithmException e) {
error("Specified digest algorithm " + digestNames[i]
+ ", but not such algorithm was found: " + e);
}
}
}
public void setPassword(String string) {
password = string;
}
public void setKeystore(File keystore) {
this.keystoreFile = keystore;
}
public void setAlias(String string) {
this.alias = string;
}
}