| /* |
| * 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); |
| } |
| |
| } |