blob: a50dd5ef259432fb8a864dac6eac57be56088124 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.extbrowser;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import org.apache.tools.ant.BuildException;
/**
* Ant task that builds CRX file (i.e., Chrome extension) from the specified
* ZIP file.
*
* @author Jan Stola
*/
public class CrxTask {
/** ZIP file from which the extension should be built. */
private File src;
/** File where the built extension should be stored. */
private File destfile;
/** Public key stored in the extension. */
private File publickey;
/** Private key used to sign the extension. */
private File privatekey;
/**
* Sets the ZIP file from which the extension should be built.
*
* @param src ZIP file from which the extension should be built.
*/
public void setSrc(File src) {
this.src = src;
}
/**
* Sets the public key/certificate stored in the extension.
*
* @param publickey public key/certificate stored in the extension.
*/
public void setPublickey(File publickey) {
this.publickey = publickey;
}
/**
* Sets the private key used to sign the extension.
*
* @param privatekey private key used to sign the extension.
*/
public void setPrivatekey(File privatekey) {
this.privatekey = privatekey;
}
/**
* Sets the file where the built extension should be stored.
*
* @param destfile file where the built extension should be stored.
*/
public void setDestfile(File destfile) {
this.destfile = destfile;
}
/**
* Returns the file where the built extension should be stored.
*
* @return file where the build extension should be stored.
*/
private File getDestfile() {
if (destfile == null) {
String zipName = src.getName();
int index = zipName.lastIndexOf('.');
String prefix = (index == -1) ? zipName : zipName.substring(0, index);
String crxName = prefix + ".crx"; // NOI18N
destfile = new File(src.getParent(), crxName);
}
return destfile;
}
/**
* Executes this task, i.e., builds the CRX file.
*/
public void execute() {
if (src == null) {
throw new BuildException("Zip file (src attribute) is not set!");
}
if (privatekey == null) {
throw new BuildException("Private key is not set!");
}
if (publickey == null) {
throw new BuildException("Public key is not set!");
}
try {
// Get all necessary components
byte[] zipBytes = readFile(src);
byte[] privateKeyBytes = pemToDer(readFile(privatekey));
byte[] publicKeyBytes = pemToDer(readFile(publickey));
PrivateKey key = createPrivateKey(privateKeyBytes);
byte[] signature = signature(zipBytes, key);
// Write the content of the CRX file
ByteBuffer bb = ByteBuffer.allocate(4);
bb.order(ByteOrder.LITTLE_ENDIAN);
FileOutputStream output = new FileOutputStream(getDestfile());
output.write("Cr24".getBytes()); // NOI18N
output.write(bb.putInt(0,2).array());
output.write(bb.putInt(0,publicKeyBytes.length).array());
output.write(bb.putInt(0,signature.length).array());
output.write(publicKeyBytes);
output.write(signature);
output.write(zipBytes);
output.close();
} catch (IOException ioex) {
throw new BuildException(ioex);
} catch (GeneralSecurityException gsex) {
throw new BuildException(gsex);
}
}
/**
* Calculates signature of the given bytes using the given key.
*
* @param bytesToSign bytes to sign.
* @param key private key used for signing.
* @return signature of the given bytes using the given key.
* @throws GeneralSecurityException when the signature cannot be calculated.
*/
private byte[] signature(byte[] bytesToSign, PrivateKey key) throws GeneralSecurityException {
Signature signature = Signature.getInstance("SHA1withRSA"); // NOI18N
signature.initSign(key);
signature.update(bytesToSign);
return signature.sign();
}
/**
* Reads the content of the file and returns it as an array of bytes.
*
* @param file file to read.
* @return content of the file as an array of bytes.
* @throws IOException when there is some problem with reading of the file.
*/
private byte[] readFile(File file) throws IOException {
int fileLength = (int)file.length();
byte[] bytes = new byte[fileLength];
DataInputStream input = new DataInputStream(new FileInputStream(file));
input.readFully(bytes);
input.close();
return bytes;
}
/**
* Converts the key/certificate from PEM to DER format.
* In other words, it removes -----BEGIN CERTIFICATE----- header and
* -----END CERTIFICATE----- footer and decodes the resulting
* BASE64-encoded string.
*
* @param bytes key/certificate in PEM format.
* @return key/certificate in DER format.
*/
private byte[] pemToDer(byte[] bytes) {
String pem = new String(bytes).trim();
int start = pem.indexOf('\n');
int end = pem.lastIndexOf('\n');
String body = pem.substring(start+1,end);
return Base64.getMimeDecoder().decode(body);
}
/**
* Creates a private key from its DER encoding.
*
* @param der DER encoding of the private key.
* @return private key corresponding to the given byte array.
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
*/
private PrivateKey createPrivateKey(byte[] der) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory factory = KeyFactory.getInstance("RSA"); // NOI18N
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(der);
return factory.generatePrivate(spec);
}
}