Use local copy of latest bndlib code for pre-release testing purposes
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1347815 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/src/main/java/aQute/bnd/signing/Signer.java b/bundleplugin/src/main/java/aQute/bnd/signing/Signer.java
new file mode 100644
index 0000000..1827cd6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/signing/Signer.java
@@ -0,0 +1,196 @@
+package aQute.bnd.signing;
+
+import java.io.*;
+import java.security.*;
+import java.security.KeyStore.PrivateKeyEntry;
+import java.util.*;
+import java.util.jar.*;
+import java.util.regex.*;
+
+import aQute.lib.base64.*;
+import aQute.lib.io.*;
+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;
+
+ java.io.FileInputStream keystoreInputStream = null;
+ try {
+ keystoreInputStream = new java.io.FileInputStream(
+ keystoreFile);
+ char[] pw = password == null ? new char[0]
+ : password.toCharArray();
+
+ keystore.load(keystoreInputStream, pw);
+ keystoreInputStream.close();
+ privateKeyEntry = (PrivateKeyEntry) keystore.getEntry(
+ alias, new KeyStore.PasswordProtection(pw));
+ } catch (Exception e) {
+ error("No able to load the private key from the give keystore("+keystoreFile.getAbsolutePath()+") with alias "+alias+" : "
+ + e);
+ return;
+ } finally {
+ IO.close(keystoreInputStream);
+ }
+ 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) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ PrintWriter ps = IO.writer(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("UTF-8"));
+ 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;
+ }
+}