summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Lawrence <paullawrence@google.com>2014-11-13 22:15:30 +0000
committerIliyan Malchev <malchev@google.com>2014-11-13 15:33:28 -0800
commit29131b97ed091bb2b10917036a64f3403c507eb7 (patch)
treed4f8dc61534ff9531099ea27d6918874886ab344
parent7377e002421ed9a04cc94cd808c234f48d93924d (diff)
downloadextras-29131b97ed091bb2b10917036a64f3403c507eb7.tar.gz
Reinstate "Update boot image signature format to version 1"
This reverts commit 7377e002421ed9a04cc94cd808c234f48d93924d. Change-Id: I4b1d83b62ae4d4dd6952663744b1171b3e0d0766 Signed-off-by: Iliyan Malchev <malchev@google.com>
-rw-r--r--verity/BootSignature.java112
-rw-r--r--verity/KeystoreSigner.java5
-rw-r--r--verity/Utils.java110
3 files changed, 202 insertions, 25 deletions
diff --git a/verity/BootSignature.java b/verity/BootSignature.java
index 740e226a..4ee3309d 100644
--- a/verity/BootSignature.java
+++ b/verity/BootSignature.java
@@ -17,50 +17,58 @@
package com.android.verity;
import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.security.PrivateKey;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.security.cert.CertificateEncodingException;
import java.util.Arrays;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERPrintableString;
import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.util.ASN1Dump;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
/**
* AndroidVerifiedBootSignature DEFINITIONS ::=
* BEGIN
- * FormatVersion ::= INTEGER
- * AlgorithmIdentifier ::= SEQUENCE {
+ * formatVersion ::= INTEGER
+ * certificate ::= Certificate
+ * algorithmIdentifier ::= SEQUENCE {
* algorithm OBJECT IDENTIFIER,
* parameters ANY DEFINED BY algorithm OPTIONAL
* }
- * AuthenticatedAttributes ::= SEQUENCE {
+ * authenticatedAttributes ::= SEQUENCE {
* target CHARACTER STRING,
* length INTEGER
* }
- * Signature ::= OCTET STRING
+ * signature ::= OCTET STRING
* END
*/
public class BootSignature extends ASN1Object
{
private ASN1Integer formatVersion;
+ private ASN1Encodable certificate;
private AlgorithmIdentifier algorithmIdentifier;
private DERPrintableString target;
private ASN1Integer length;
private DEROctetString signature;
+ private static final int FORMAT_VERSION = 1;
+
public BootSignature(String target, int length) {
- this.formatVersion = new ASN1Integer(0);
+ this.formatVersion = new ASN1Integer(FORMAT_VERSION);
this.target = new DERPrintableString(target);
this.length = new ASN1Integer(length);
- this.algorithmIdentifier = new AlgorithmIdentifier(
- PKCSObjectIdentifiers.sha1WithRSAEncryption);
}
public ASN1Object getAuthenticatedAttributes() {
@@ -74,10 +82,17 @@ public class BootSignature extends ASN1Object
return getAuthenticatedAttributes().getEncoded();
}
- public void setSignature(byte[] sig) {
+ public void setSignature(byte[] sig, AlgorithmIdentifier algId) {
+ algorithmIdentifier = algId;
signature = new DEROctetString(sig);
}
+ public void setCertificate(X509Certificate cert)
+ throws Exception, IOException, CertificateEncodingException {
+ ASN1InputStream s = new ASN1InputStream(cert.getEncoded());
+ certificate = s.readObject();
+ }
+
public byte[] generateSignableImage(byte[] image) throws IOException {
byte[] attrs = getEncodedAuthenticatedAttributes();
byte[] signable = Arrays.copyOf(image, image.length + attrs.length);
@@ -95,30 +110,93 @@ public class BootSignature extends ASN1Object
public ASN1Primitive toASN1Primitive() {
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(formatVersion);
+ v.add(certificate);
v.add(algorithmIdentifier);
v.add(getAuthenticatedAttributes());
v.add(signature);
return new DERSequence(v);
}
+ public static int getSignableImageSize(byte[] data) throws Exception {
+ if (!Arrays.equals(Arrays.copyOfRange(data, 0, 8),
+ "ANDROID!".getBytes("US-ASCII"))) {
+ throw new IllegalArgumentException("Invalid image header: missing magic");
+ }
+
+ ByteBuffer image = ByteBuffer.wrap(data);
+ image.order(ByteOrder.LITTLE_ENDIAN);
+
+ image.getLong(); // magic
+ int kernelSize = image.getInt();
+ image.getInt(); // kernel_addr
+ int ramdskSize = image.getInt();
+ image.getInt(); // ramdisk_addr
+ int secondSize = image.getInt();
+ image.getLong(); // second_addr + tags_addr
+ int pageSize = image.getInt();
+
+ int length = pageSize // include the page aligned image header
+ + ((kernelSize + pageSize - 1) / pageSize) * pageSize
+ + ((ramdskSize + pageSize - 1) / pageSize) * pageSize
+ + ((secondSize + pageSize - 1) / pageSize) * pageSize;
+
+ length = ((length + pageSize - 1) / pageSize) * pageSize;
+
+ if (length <= 0) {
+ throw new IllegalArgumentException("Invalid image header: invalid length");
+ }
+
+ return length;
+ }
+
public static void doSignature( String target,
String imagePath,
String keyPath,
+ String certPath,
String outPath) throws Exception {
+
byte[] image = Utils.read(imagePath);
+ int signableSize = getSignableImageSize(image);
+
+ if (signableSize < image.length) {
+ System.err.println("NOTE: truncating file " + imagePath +
+ " from " + image.length + " to " + signableSize + " bytes");
+ image = Arrays.copyOf(image, signableSize);
+ } else if (signableSize > image.length) {
+ throw new IllegalArgumentException("Invalid image: too short, expected " +
+ signableSize + " bytes");
+ }
+
BootSignature bootsig = new BootSignature(target, image.length);
- PrivateKey key = Utils.loadPEMPrivateKeyFromFile(keyPath);
- bootsig.setSignature(bootsig.sign(image, key));
+
+ X509Certificate cert = Utils.loadPEMCertificate(certPath);
+ bootsig.setCertificate(cert);
+
+ PrivateKey key = Utils.loadDERPrivateKeyFromFile(keyPath);
+ bootsig.setSignature(bootsig.sign(image, key),
+ Utils.getSignatureAlgorithmIdentifier(key));
+
byte[] encoded_bootsig = bootsig.getEncoded();
byte[] image_with_metadata = Arrays.copyOf(image, image.length + encoded_bootsig.length);
- for (int i=0; i < encoded_bootsig.length; i++) {
- image_with_metadata[i+image.length] = encoded_bootsig[i];
- }
+
+ System.arraycopy(encoded_bootsig, 0, image_with_metadata,
+ image.length, encoded_bootsig.length);
+
Utils.write(image_with_metadata, outPath);
}
- // java -cp ../../../out/host/common/obj/JAVA_LIBRARIES/AndroidVerifiedBootSigner_intermediates/classes/ com.android.verity.AndroidVerifiedBootSigner boot ../../../out/target/product/flounder/boot.img ../../../build/target/product/security/verity_private_dev_key /tmp/boot.img.signed
+ /* java -cp
+ ../../../out/host/common/obj/JAVA_LIBRARIES/BootSignature_intermediates/\
+ classes/com.android.verity.BootSignature \
+ boot \
+ ../../../out/target/product/flounder/boot.img \
+ ../../../build/target/product/security/verity_private_dev_key \
+ ../../../build/target/product/security/verity.pk8 \
+ ../../../build/target/product/security/verity.x509.pem \
+ /tmp/boot.img.signed
+ */
public static void main(String[] args) throws Exception {
- doSignature(args[0], args[1], args[2], args[3]);
+ Security.addProvider(new BouncyCastleProvider());
+ doSignature(args[0], args[1], args[2], args[3], args[4]);
}
-} \ No newline at end of file
+}
diff --git a/verity/KeystoreSigner.java b/verity/KeystoreSigner.java
index d57f328f..c020fb60 100644
--- a/verity/KeystoreSigner.java
+++ b/verity/KeystoreSigner.java
@@ -113,7 +113,8 @@ class BootKeystore extends ASN1Object
byte[] innerKeystore = getInnerKeystore();
byte[] rawSignature = Utils.sign(privateKey, innerKeystore);
signature = new BootSignature("keystore", innerKeystore.length);
- signature.setSignature(rawSignature);
+ signature.setSignature(rawSignature,
+ new AlgorithmIdentifier(PKCSObjectIdentifiers.sha1WithRSAEncryption));
}
public void dump() throws Exception {
@@ -134,4 +135,4 @@ class BootKeystore extends ASN1Object
ks.sign(Utils.loadPEMPrivateKeyFromFile(privkeyFname));
Utils.write(ks.getEncoded(), outfileFname);
}
-} \ No newline at end of file
+}
diff --git a/verity/Utils.java b/verity/Utils.java
index 2c1e7bb4..4eba5527 100644
--- a/verity/Utils.java
+++ b/verity/Utils.java
@@ -18,22 +18,60 @@ package com.android.verity;
import java.lang.reflect.Constructor;
import java.io.File;
+import java.io.ByteArrayInputStream;
+import java.io.Console;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.io.InputStreamReader;
import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.KeyFactory;
import java.security.Provider;
import java.security.Security;
import java.security.Signature;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
import java.security.spec.X509EncodedKeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
-
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.crypto.Cipher;
+import javax.crypto.EncryptedPrivateKeyInfo;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.util.encoders.Base64;
public class Utils {
+ private static final Map<String, String> ID_TO_ALG;
+ private static final Map<String, String> ALG_TO_ID;
+
+ static {
+ ID_TO_ALG = new HashMap<String, String>();
+ ALG_TO_ID = new HashMap<String, String>();
+
+ ID_TO_ALG.put(PKCSObjectIdentifiers.sha1WithRSAEncryption.getId(), "SHA1withRSA");
+ ID_TO_ALG.put(PKCSObjectIdentifiers.sha256WithRSAEncryption.getId(), "SHA256withRSA");
+ ID_TO_ALG.put(PKCSObjectIdentifiers.sha512WithRSAEncryption.getId(), "SHA512withRSA");
+
+ ALG_TO_ID.put("SHA1withRSA", PKCSObjectIdentifiers.sha1WithRSAEncryption.getId());
+ ALG_TO_ID.put("SHA256withRSA", PKCSObjectIdentifiers.sha256WithRSAEncryption.getId());
+ ALG_TO_ID.put("SHA512withRSA", PKCSObjectIdentifiers.sha512WithRSAEncryption.getId());
+ }
+
private static void loadProviderIfNecessary(String providerClassName) {
if (providerClassName == null) {
return;
@@ -88,10 +126,45 @@ public class Utils {
return Base64.decode(base64_der);
}
+ private static PKCS8EncodedKeySpec decryptPrivateKey(byte[] encryptedPrivateKey)
+ throws GeneralSecurityException {
+ EncryptedPrivateKeyInfo epkInfo;
+ try {
+ epkInfo = new EncryptedPrivateKeyInfo(encryptedPrivateKey);
+ } catch (IOException ex) {
+ // Probably not an encrypted key.
+ return null;
+ }
+
+ char[] password = System.console().readPassword("Password for the private key file: ");
+
+ SecretKeyFactory skFactory = SecretKeyFactory.getInstance(epkInfo.getAlgName());
+ Key key = skFactory.generateSecret(new PBEKeySpec(password));
+ Arrays.fill(password, '\0');
+
+ Cipher cipher = Cipher.getInstance(epkInfo.getAlgName());
+ cipher.init(Cipher.DECRYPT_MODE, key, epkInfo.getAlgParameters());
+
+ try {
+ return epkInfo.getKeySpec(cipher);
+ } catch (InvalidKeySpecException ex) {
+ System.err.println("Password may be bad.");
+ throw ex;
+ }
+ }
+
static PrivateKey loadDERPrivateKey(byte[] der) throws Exception {
- PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(der);
- KeyFactory keyFactory = KeyFactory.getInstance("RSA");
- return (PrivateKey) keyFactory.generatePrivate(keySpec);
+ PKCS8EncodedKeySpec spec = decryptPrivateKey(der);
+
+ if (spec == null) {
+ spec = new PKCS8EncodedKeySpec(der);
+ }
+
+ ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(spec.getEncoded()));
+ PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
+ String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId();
+
+ return KeyFactory.getInstance(algOid).generatePrivate(spec);
}
static PrivateKey loadPEMPrivateKey(byte[] pem) throws Exception {
@@ -128,8 +201,33 @@ public class Utils {
return loadDERPublicKey(read(keyFname));
}
+ static X509Certificate loadPEMCertificate(String fname) throws Exception {
+ try (FileInputStream fis = new FileInputStream(fname)) {
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ return (X509Certificate) cf.generateCertificate(fis);
+ }
+ }
+
+ private static String getSignatureAlgorithm(Key key) {
+ if ("RSA".equals(key.getAlgorithm())) {
+ return "SHA256withRSA";
+ } else {
+ throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm());
+ }
+ }
+
+ static AlgorithmIdentifier getSignatureAlgorithmIdentifier(Key key) {
+ String id = ALG_TO_ID.get(getSignatureAlgorithm(key));
+
+ if (id == null) {
+ throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm());
+ }
+
+ return new AlgorithmIdentifier(new ASN1ObjectIdentifier(id));
+ }
+
static byte[] sign(PrivateKey privateKey, byte[] input) throws Exception {
- Signature signer = Signature.getInstance("SHA1withRSA");
+ Signature signer = Signature.getInstance(getSignatureAlgorithm(privateKey));
signer.initSign(privateKey);
signer.update(input);
return signer.sign();
@@ -153,4 +251,4 @@ public class Utils {
out.write(data);
out.close();
}
-} \ No newline at end of file
+}