diff options
author | Geremy Condra <gcondra@google.com> | 2014-06-11 13:38:45 -0700 |
---|---|---|
committer | Geremy Condra <gcondra@google.com> | 2014-07-08 21:36:25 -0700 |
commit | cee5bfdf119104b8ebce56d54dfcdcca1f537075 (patch) | |
tree | 5b7cb9e9f885f7c5a197dca184fc5d473d03e901 | |
parent | a2b7dbef923dbc1652fbb71969416cdd7adb40df (diff) | |
download | extras-cee5bfdf119104b8ebce56d54dfcdcca1f537075.tar.gz |
verity: Add tools to help OEMs generate signed boot images.
Change-Id: Iea200def2fdd8a0d366888bb7b1ae401297063f1
-rw-r--r-- | verity/Android.mk | 31 | ||||
-rw-r--r-- | verity/BootSignature.java | 124 | ||||
-rw-r--r-- | verity/BootSignature.mf | 1 | ||||
-rw-r--r-- | verity/KeystoreSigner.java | 137 | ||||
-rw-r--r-- | verity/KeystoreSigner.mf | 1 | ||||
-rw-r--r-- | verity/Utils.java | 156 | ||||
-rw-r--r-- | verity/VeritySigner.java | 55 | ||||
-rwxr-xr-x | verity/boot_signer | 8 | ||||
-rw-r--r-- | verity/keystore_signer | 8 |
9 files changed, 468 insertions, 53 deletions
diff --git a/verity/Android.mk b/verity/Android.mk index ba986262..5d37f0c7 100644 --- a/verity/Android.mk +++ b/verity/Android.mk @@ -10,7 +10,7 @@ LOCAL_C_INCLUDES += external/openssl/include include $(BUILD_HOST_EXECUTABLE) include $(CLEAR_VARS) -LOCAL_SRC_FILES := VeritySigner.java +LOCAL_SRC_FILES := VeritySigner.java Utils.java LOCAL_MODULE := VeritySigner LOCAL_JAR_MANIFEST := VeritySigner.mf LOCAL_MODULE_TAGS := optional @@ -18,6 +18,22 @@ LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle-host include $(BUILD_HOST_JAVA_LIBRARY) include $(CLEAR_VARS) +LOCAL_SRC_FILES := BootSignature.java VeritySigner.java Utils.java +LOCAL_MODULE := BootSignature +LOCAL_JAR_MANIFEST := BootSignature.mf +LOCAL_MODULE_TAGS := optional +LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle-host +include $(BUILD_HOST_JAVA_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := BootSignature.java KeystoreSigner.java Utils.java +LOCAL_MODULE := BootKeystoreSigner +LOCAL_JAR_MANIFEST := KeystoreSigner.mf +LOCAL_MODULE_TAGS := optional +LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle-host +include $(BUILD_HOST_JAVA_LIBRARY) + +include $(CLEAR_VARS) LOCAL_SRC_FILES := verity_signer LOCAL_MODULE := verity_signer LOCAL_MODULE_CLASS := EXECUTABLES @@ -27,12 +43,21 @@ LOCAL_REQUIRED_MODULES := VeritySigner include $(BUILD_PREBUILT) include $(CLEAR_VARS) -LOCAL_MODULE := build_verity_tree.py +LOCAL_SRC_FILES := boot_signer +LOCAL_MODULE := boot_signer LOCAL_MODULE_CLASS := EXECUTABLES -LOCAL_SRC_FILES := build_verity_tree.py +LOCAL_IS_HOST_MODULE := true +LOCAL_MODULE_TAGS := optional +LOCAL_REQUIRED_MODULES := BootSigner +include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := keystore_signer +LOCAL_MODULE := keystore_signer LOCAL_MODULE_CLASS := EXECUTABLES LOCAL_IS_HOST_MODULE := true LOCAL_MODULE_TAGS := optional +LOCAL_REQUIRED_MODULES := KeystoreSigner include $(BUILD_PREBUILT) include $(CLEAR_VARS) diff --git a/verity/BootSignature.java b/verity/BootSignature.java new file mode 100644 index 00000000..f5ceb304 --- /dev/null +++ b/verity/BootSignature.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed 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 com.android.verity; + +import java.io.IOException; +import java.security.PrivateKey; +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.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; + +/** + * AndroidVerifiedBootSignature DEFINITIONS ::= + * BEGIN + * FormatVersion ::= INTEGER + * AlgorithmIdentifier ::= SEQUENCE { + * algorithm OBJECT IDENTIFIER, + * parameters ANY DEFINED BY algorithm OPTIONAL + * } + * AuthenticatedAttributes ::= SEQUENCE { + * target CHARACTER STRING, + * length INTEGER + * } + * Signature ::= OCTET STRING + * END + */ + +public class BootSignature extends ASN1Object +{ + private ASN1Integer formatVersion; + private AlgorithmIdentifier algorithmIdentifier; + private DERPrintableString target; + private ASN1Integer length; + private DEROctetString signature; + + public BootSignature(String target, int length) { + this.formatVersion = new ASN1Integer(0); + this.target = new DERPrintableString(target); + this.length = new ASN1Integer(length); + this.algorithmIdentifier = new AlgorithmIdentifier( + PKCSObjectIdentifiers.sha256WithRSAEncryption); + } + + public ASN1Object getAuthenticatedAttributes() { + ASN1EncodableVector attrs = new ASN1EncodableVector(); + attrs.add(target); + attrs.add(length); + return new DERSequence(attrs); + } + + public byte[] getEncodedAuthenticatedAttributes() throws IOException { + return getAuthenticatedAttributes().getEncoded(); + } + + public void setSignature(byte[] sig) { + signature = new DEROctetString(sig); + } + + public byte[] generateSignableImage(byte[] image) throws IOException { + byte[] attrs = getEncodedAuthenticatedAttributes(); + byte[] signable = Arrays.copyOf(image, image.length + attrs.length); + for (int i=0; i < attrs.length; i++) { + signable[i+image.length] = attrs[i]; + } + return signable; + } + + public byte[] sign(byte[] image, PrivateKey key) throws Exception { + byte[] signable = generateSignableImage(image); + byte[] signature = Utils.sign(key, signable); + byte[] signed = Arrays.copyOf(image, image.length + signature.length); + for (int i=0; i < signature.length; i++) { + signed[i+image.length] = signature[i]; + } + return signed; + } + + public ASN1Primitive toASN1Primitive() { + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(formatVersion); + v.add(algorithmIdentifier); + v.add(getAuthenticatedAttributes()); + v.add(signature); + return new DERSequence(v); + } + + public static void doSignature( String target, + String imagePath, + String keyPath, + String outPath) throws Exception { + byte[] image = Utils.read(imagePath); + BootSignature bootsig = new BootSignature(target, image.length); + PrivateKey key = Utils.loadPEMPrivateKeyFromFile(keyPath); + byte[] signature = bootsig.sign(image, key); + Utils.write(signature, 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 + public static void main(String[] args) throws Exception { + doSignature(args[0], args[1], args[2], args[3]); + } +}
\ No newline at end of file diff --git a/verity/BootSignature.mf b/verity/BootSignature.mf new file mode 100644 index 00000000..c1868b6c --- /dev/null +++ b/verity/BootSignature.mf @@ -0,0 +1 @@ +Main-Class: com.android.verity.BootSignature diff --git a/verity/KeystoreSigner.java b/verity/KeystoreSigner.java new file mode 100644 index 00000000..d57f328f --- /dev/null +++ b/verity/KeystoreSigner.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed 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 com.android.verity; + +import java.io.IOException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +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.DEROctetString; +import org.bouncycastle.asn1.DERPrintableString; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.RSAPublicKey; +import org.bouncycastle.asn1.util.ASN1Dump; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; + +/** + * AndroidVerifiedBootKeystore DEFINITIONS ::= + * BEGIN + * FormatVersion ::= INTEGER + * KeyBag ::= SEQUENCE { + * Key ::= SEQUENCE { + * AlgorithmIdentifier ::= SEQUENCE { + * algorithm OBJECT IDENTIFIER, + * parameters ANY DEFINED BY algorithm OPTIONAL + * } + * KeyMaterial ::= RSAPublicKey + * } + * } + * Signature ::= AndroidVerifiedBootSignature + * END + */ + +class BootKey extends ASN1Object +{ + private AlgorithmIdentifier algorithmIdentifier; + private RSAPublicKey keyMaterial; + + public BootKey(PublicKey key) throws Exception { + java.security.interfaces.RSAPublicKey k = + (java.security.interfaces.RSAPublicKey) key; + this.keyMaterial = new RSAPublicKey( + k.getModulus(), + k.getPublicExponent()); + this.algorithmIdentifier = new AlgorithmIdentifier( + PKCSObjectIdentifiers.sha256WithRSAEncryption); + } + + public ASN1Primitive toASN1Primitive() { + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(algorithmIdentifier); + v.add(keyMaterial); + return new DERSequence(v); + } + + public void dump() throws Exception { + System.out.println(ASN1Dump.dumpAsString(toASN1Primitive())); + } +} + +class BootKeystore extends ASN1Object +{ + private ASN1Integer formatVersion; + private ASN1EncodableVector keyBag; + private BootSignature signature; + + public BootKeystore() { + this.formatVersion = new ASN1Integer(0); + this.keyBag = new ASN1EncodableVector(); + } + + public void addPublicKey(byte[] der) throws Exception { + PublicKey pubkey = Utils.loadDERPublicKey(der); + BootKey k = new BootKey(pubkey); + keyBag.add(k); + } + + public byte[] getInnerKeystore() throws Exception { + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(formatVersion); + v.add(new DERSequence(keyBag)); + return new DERSequence(v).getEncoded(); + } + + public ASN1Primitive toASN1Primitive() { + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(formatVersion); + v.add(new DERSequence(keyBag)); + v.add(signature); + return new DERSequence(v); + } + + public void sign(PrivateKey privateKey) throws Exception { + byte[] innerKeystore = getInnerKeystore(); + byte[] rawSignature = Utils.sign(privateKey, innerKeystore); + signature = new BootSignature("keystore", innerKeystore.length); + signature.setSignature(rawSignature); + } + + public void dump() throws Exception { + System.out.println(ASN1Dump.dumpAsString(toASN1Primitive())); + } + + // USAGE: + // AndroidVerifiedBootKeystoreSigner <privkeyFile> <outfile> <pubkeyFile0> ... <pubkeyFileN-1> + // EG: + // java -cp ../../../out/host/common/obj/JAVA_LIBRARIES/AndroidVerifiedBootKeystoreSigner_intermediates/classes/ com.android.verity.AndroidVerifiedBootKeystoreSigner ../../../build/target/product/security/verity_private_dev_key /tmp/keystore.out /tmp/k + public static void main(String[] args) throws Exception { + String privkeyFname = args[0]; + String outfileFname = args[1]; + BootKeystore ks = new BootKeystore(); + for (int i=2; i < args.length; i++) { + ks.addPublicKey(Utils.read(args[i])); + } + ks.sign(Utils.loadPEMPrivateKeyFromFile(privkeyFname)); + Utils.write(ks.getEncoded(), outfileFname); + } +}
\ No newline at end of file diff --git a/verity/KeystoreSigner.mf b/verity/KeystoreSigner.mf new file mode 100644 index 00000000..a4fee27f --- /dev/null +++ b/verity/KeystoreSigner.mf @@ -0,0 +1 @@ +Main-Class: com.android.verity.KeystoreSigner
\ No newline at end of file diff --git a/verity/Utils.java b/verity/Utils.java new file mode 100644 index 00000000..2c1e7bb4 --- /dev/null +++ b/verity/Utils.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed 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 com.android.verity; + +import java.lang.reflect.Constructor; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +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.spec.X509EncodedKeySpec; +import java.security.spec.PKCS8EncodedKeySpec; + +import org.bouncycastle.util.encoders.Base64; + +public class Utils { + + private static void loadProviderIfNecessary(String providerClassName) { + if (providerClassName == null) { + return; + } + + final Class<?> klass; + try { + final ClassLoader sysLoader = ClassLoader.getSystemClassLoader(); + if (sysLoader != null) { + klass = sysLoader.loadClass(providerClassName); + } else { + klass = Class.forName(providerClassName); + } + } catch (ClassNotFoundException e) { + e.printStackTrace(); + System.exit(1); + return; + } + + Constructor<?> constructor = null; + for (Constructor<?> c : klass.getConstructors()) { + if (c.getParameterTypes().length == 0) { + constructor = c; + break; + } + } + if (constructor == null) { + System.err.println("No zero-arg constructor found for " + providerClassName); + System.exit(1); + return; + } + + final Object o; + try { + o = constructor.newInstance(); + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + return; + } + if (!(o instanceof Provider)) { + System.err.println("Not a Provider class: " + providerClassName); + System.exit(1); + } + + Security.insertProviderAt((Provider) o, 1); + } + + static byte[] pemToDer(String pem) throws Exception { + pem = pem.replaceAll("^-.*", ""); + String base64_der = pem.replaceAll("-.*$", ""); + return Base64.decode(base64_der); + } + + static PrivateKey loadDERPrivateKey(byte[] der) throws Exception { + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(der); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return (PrivateKey) keyFactory.generatePrivate(keySpec); + } + + static PrivateKey loadPEMPrivateKey(byte[] pem) throws Exception { + byte[] der = pemToDer(new String(pem)); + return loadDERPrivateKey(der); + } + + static PrivateKey loadPEMPrivateKeyFromFile(String keyFname) throws Exception { + return loadPEMPrivateKey(read(keyFname)); + } + + static PrivateKey loadDERPrivateKeyFromFile(String keyFname) throws Exception { + return loadDERPrivateKey(read(keyFname)); + } + + static PublicKey loadDERPublicKey(byte[] der) throws Exception { + X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(der); + KeyFactory factory = KeyFactory.getInstance("RSA"); + return factory.generatePublic(publicKeySpec); + } + + static PublicKey loadPEMPublicKey(byte[] pem) throws Exception { + byte[] der = pemToDer(new String(pem)); + X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(der); + KeyFactory factory = KeyFactory.getInstance("RSA"); + return factory.generatePublic(publicKeySpec); + } + + static PublicKey loadPEMPublicKeyFromFile(String keyFname) throws Exception { + return loadPEMPublicKey(read(keyFname)); + } + + static PublicKey loadDERPublicKeyFromFile(String keyFname) throws Exception { + return loadDERPublicKey(read(keyFname)); + } + + static byte[] sign(PrivateKey privateKey, byte[] input) throws Exception { + Signature signer = Signature.getInstance("SHA1withRSA"); + signer.initSign(privateKey); + signer.update(input); + return signer.sign(); + } + + static byte[] read(String fname) throws Exception { + long offset = 0; + File f = new File(fname); + long length = f.length(); + byte[] image = new byte[(int)length]; + FileInputStream fis = new FileInputStream(f); + while (offset < length) { + offset += fis.read(image, (int)offset, (int)(length - offset)); + } + fis.close(); + return image; + } + + static void write(byte[] data, String fname) throws Exception{ + FileOutputStream out = new FileOutputStream(fname); + out.write(data); + out.close(); + } +}
\ No newline at end of file diff --git a/verity/VeritySigner.java b/verity/VeritySigner.java index 2ab94cb4..44c56028 100644 --- a/verity/VeritySigner.java +++ b/verity/VeritySigner.java @@ -16,63 +16,18 @@ package com.android.verity; -import org.bouncycastle.util.encoders.Base64; - -import java.io.DataInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.security.KeyFactory; import java.security.PrivateKey; -import java.security.Signature; -import java.security.spec.PKCS8EncodedKeySpec; - -class VeritySigner { - - private static byte[] sign(PrivateKey privateKey, byte[] input) throws Exception { - Signature signer = Signature.getInstance("SHA1withRSA"); - signer.initSign(privateKey); - signer.update(input); - return signer.sign(); - } - - private static PKCS8EncodedKeySpec pemToDer(String pem) throws Exception { - pem = pem.replaceAll("^-.*", ""); - String base64_der = pem.replaceAll("-.*$", ""); - byte[] der = Base64.decode(base64_der); - return new PKCS8EncodedKeySpec(der); - } - private static PrivateKey loadPrivateKey(String pem) throws Exception { - PKCS8EncodedKeySpec keySpec = pemToDer(pem); - KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - return (PrivateKey) keyFactory.generatePrivate(keySpec); - } - - private static byte[] read(String path) throws Exception { - File contentFile = new File(path); - byte[] content = new byte[(int)contentFile.length()]; - FileInputStream fis = new FileInputStream(contentFile); - fis.read(content); - fis.close(); - return content; - } - - private static void writeOutput(String path, byte[] output) throws Exception { - FileOutputStream fos = new FileOutputStream(path); - fos.write(output); - fos.close(); - } +public class VeritySigner { // USAGE: // VeritySigner <contentfile> <key.pem> <sigfile> // To verify that this has correct output: // openssl rsautl -raw -inkey <key.pem> -encrypt -in <sigfile> > /tmp/dump public static void main(String[] args) throws Exception { - byte[] content = read(args[0]); - PrivateKey privateKey = loadPrivateKey(new String(read(args[1]))); - byte[] signature = sign(privateKey, content); - writeOutput(args[2], signature); + byte[] content = Utils.read(args[0]); + PrivateKey privateKey = Utils.loadPEMPrivateKey(Utils.read(args[1])); + byte[] signature = Utils.sign(privateKey, content); + Utils.write(signature, args[2]); } } diff --git a/verity/boot_signer b/verity/boot_signer new file mode 100755 index 00000000..e2ee42bf --- /dev/null +++ b/verity/boot_signer @@ -0,0 +1,8 @@ +#! /bin/sh + +# Start-up script for BootSigner + +BOOTSIGNER_HOME=`dirname "$0"` +BOOTSIGNER_HOME=`dirname "$BOOTSIGNER_HOME"` + +java -Xmx512M -jar "$BOOTSIGNER_HOME"/framework/BootSignature.jar "$@"
\ No newline at end of file diff --git a/verity/keystore_signer b/verity/keystore_signer new file mode 100644 index 00000000..7619c546 --- /dev/null +++ b/verity/keystore_signer @@ -0,0 +1,8 @@ +#! /bin/sh + +# Start-up script for KeystoreSigner + +KEYSTORESIGNER_HOME=`dirname "$0"` +KEYSTORESIGNER_HOME=`dirname "$KEYSTORESIGNER_HOME"` + +java -Xmx512M -jar "$KEYSTORESIGNER_HOME"/framework/KeystoreSigner.jar "$@"
\ No newline at end of file |