| 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; |
| } |
| } |