Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2014 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.verity; |
| 18 | |
Sami Tolvanen | 3380f2f | 2014-10-31 17:18:23 -0700 | [diff] [blame] | 19 | import java.io.ByteArrayInputStream; |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 20 | import java.io.IOException; |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 21 | import java.nio.ByteBuffer; |
| 22 | import java.nio.ByteOrder; |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 23 | import java.security.PrivateKey; |
Sami Tolvanen | 3380f2f | 2014-10-31 17:18:23 -0700 | [diff] [blame] | 24 | import java.security.PublicKey; |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 25 | import java.security.Security; |
| 26 | import java.security.cert.X509Certificate; |
Sami Tolvanen | 3380f2f | 2014-10-31 17:18:23 -0700 | [diff] [blame] | 27 | import java.security.cert.Certificate; |
| 28 | import java.security.cert.CertificateFactory; |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 29 | import java.security.cert.CertificateEncodingException; |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 30 | import java.util.Arrays; |
| 31 | import org.bouncycastle.asn1.ASN1Encodable; |
| 32 | import org.bouncycastle.asn1.ASN1EncodableVector; |
| 33 | import org.bouncycastle.asn1.ASN1Integer; |
| 34 | import org.bouncycastle.asn1.ASN1Object; |
Sami Tolvanen | 3380f2f | 2014-10-31 17:18:23 -0700 | [diff] [blame] | 35 | import org.bouncycastle.asn1.ASN1ObjectIdentifier; |
| 36 | import org.bouncycastle.asn1.ASN1OctetString; |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 37 | import org.bouncycastle.asn1.ASN1Primitive; |
Sami Tolvanen | 3380f2f | 2014-10-31 17:18:23 -0700 | [diff] [blame] | 38 | import org.bouncycastle.asn1.ASN1Sequence; |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 39 | import org.bouncycastle.asn1.ASN1InputStream; |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 40 | import org.bouncycastle.asn1.DEROctetString; |
| 41 | import org.bouncycastle.asn1.DERPrintableString; |
| 42 | import org.bouncycastle.asn1.DERSequence; |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 43 | import org.bouncycastle.asn1.util.ASN1Dump; |
| 44 | import org.bouncycastle.asn1.x509.AlgorithmIdentifier; |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 45 | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 46 | |
| 47 | /** |
| 48 | * AndroidVerifiedBootSignature DEFINITIONS ::= |
| 49 | * BEGIN |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 50 | * formatVersion ::= INTEGER |
| 51 | * certificate ::= Certificate |
| 52 | * algorithmIdentifier ::= SEQUENCE { |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 53 | * algorithm OBJECT IDENTIFIER, |
| 54 | * parameters ANY DEFINED BY algorithm OPTIONAL |
| 55 | * } |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 56 | * authenticatedAttributes ::= SEQUENCE { |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 57 | * target CHARACTER STRING, |
| 58 | * length INTEGER |
| 59 | * } |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 60 | * signature ::= OCTET STRING |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 61 | * END |
| 62 | */ |
| 63 | |
| 64 | public class BootSignature extends ASN1Object |
| 65 | { |
| 66 | private ASN1Integer formatVersion; |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 67 | private ASN1Encodable certificate; |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 68 | private AlgorithmIdentifier algorithmIdentifier; |
| 69 | private DERPrintableString target; |
| 70 | private ASN1Integer length; |
| 71 | private DEROctetString signature; |
Sami Tolvanen | 3380f2f | 2014-10-31 17:18:23 -0700 | [diff] [blame] | 72 | private PublicKey publicKey; |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 73 | |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 74 | private static final int FORMAT_VERSION = 1; |
| 75 | |
Sami Tolvanen | 3380f2f | 2014-10-31 17:18:23 -0700 | [diff] [blame] | 76 | /** |
| 77 | * Initializes the object for signing an image file |
| 78 | * @param target Target name, included in the signed data |
| 79 | * @param length Length of the image, included in the signed data |
| 80 | */ |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 81 | public BootSignature(String target, int length) { |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 82 | this.formatVersion = new ASN1Integer(FORMAT_VERSION); |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 83 | this.target = new DERPrintableString(target); |
| 84 | this.length = new ASN1Integer(length); |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 85 | } |
| 86 | |
Sami Tolvanen | 3380f2f | 2014-10-31 17:18:23 -0700 | [diff] [blame] | 87 | /** |
| 88 | * Initializes the object for verifying a signed image file |
| 89 | * @param signature Signature footer |
| 90 | */ |
| 91 | public BootSignature(byte[] signature) |
| 92 | throws Exception { |
| 93 | ASN1InputStream stream = new ASN1InputStream(signature); |
| 94 | ASN1Sequence sequence = (ASN1Sequence) stream.readObject(); |
| 95 | |
| 96 | formatVersion = (ASN1Integer) sequence.getObjectAt(0); |
| 97 | if (formatVersion.getValue().intValue() != FORMAT_VERSION) { |
| 98 | throw new IllegalArgumentException("Unsupported format version"); |
| 99 | } |
| 100 | |
| 101 | certificate = sequence.getObjectAt(1); |
| 102 | byte[] encoded = ((ASN1Object) certificate).getEncoded(); |
| 103 | ByteArrayInputStream bis = new ByteArrayInputStream(encoded); |
| 104 | |
| 105 | CertificateFactory cf = CertificateFactory.getInstance("X.509"); |
| 106 | X509Certificate c = (X509Certificate) cf.generateCertificate(bis); |
| 107 | publicKey = c.getPublicKey(); |
| 108 | |
| 109 | ASN1Sequence algId = (ASN1Sequence) sequence.getObjectAt(2); |
| 110 | algorithmIdentifier = new AlgorithmIdentifier( |
| 111 | (ASN1ObjectIdentifier) algId.getObjectAt(0)); |
| 112 | |
| 113 | ASN1Sequence attrs = (ASN1Sequence) sequence.getObjectAt(3); |
| 114 | target = (DERPrintableString) attrs.getObjectAt(0); |
| 115 | length = (ASN1Integer) attrs.getObjectAt(1); |
| 116 | |
| 117 | this.signature = (DEROctetString) sequence.getObjectAt(4); |
| 118 | } |
| 119 | |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 120 | public ASN1Object getAuthenticatedAttributes() { |
| 121 | ASN1EncodableVector attrs = new ASN1EncodableVector(); |
| 122 | attrs.add(target); |
| 123 | attrs.add(length); |
| 124 | return new DERSequence(attrs); |
| 125 | } |
| 126 | |
| 127 | public byte[] getEncodedAuthenticatedAttributes() throws IOException { |
| 128 | return getAuthenticatedAttributes().getEncoded(); |
| 129 | } |
| 130 | |
Sami Tolvanen | 241f964 | 2014-11-14 14:51:25 +0000 | [diff] [blame] | 131 | public AlgorithmIdentifier getAlgorithmIdentifier() { |
| 132 | return algorithmIdentifier; |
| 133 | } |
| 134 | |
| 135 | public PublicKey getPublicKey() { |
| 136 | return publicKey; |
| 137 | } |
| 138 | |
| 139 | public byte[] getSignature() { |
| 140 | return signature.getOctets(); |
| 141 | } |
| 142 | |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 143 | public void setSignature(byte[] sig, AlgorithmIdentifier algId) { |
| 144 | algorithmIdentifier = algId; |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 145 | signature = new DEROctetString(sig); |
| 146 | } |
| 147 | |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 148 | public void setCertificate(X509Certificate cert) |
| 149 | throws Exception, IOException, CertificateEncodingException { |
| 150 | ASN1InputStream s = new ASN1InputStream(cert.getEncoded()); |
| 151 | certificate = s.readObject(); |
| 152 | } |
| 153 | |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 154 | public byte[] generateSignableImage(byte[] image) throws IOException { |
| 155 | byte[] attrs = getEncodedAuthenticatedAttributes(); |
| 156 | byte[] signable = Arrays.copyOf(image, image.length + attrs.length); |
| 157 | for (int i=0; i < attrs.length; i++) { |
| 158 | signable[i+image.length] = attrs[i]; |
| 159 | } |
| 160 | return signable; |
| 161 | } |
| 162 | |
| 163 | public byte[] sign(byte[] image, PrivateKey key) throws Exception { |
| 164 | byte[] signable = generateSignableImage(image); |
Geremy Condra | d66cefd | 2014-08-14 16:44:31 -0700 | [diff] [blame] | 165 | return Utils.sign(key, signable); |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 166 | } |
| 167 | |
Sami Tolvanen | 3380f2f | 2014-10-31 17:18:23 -0700 | [diff] [blame] | 168 | public boolean verify(byte[] image) throws Exception { |
| 169 | if (length.getValue().intValue() != image.length) { |
| 170 | throw new IllegalArgumentException("Invalid image length"); |
| 171 | } |
| 172 | |
| 173 | byte[] signable = generateSignableImage(image); |
| 174 | return Utils.verify(publicKey, signable, signature.getOctets(), |
| 175 | algorithmIdentifier); |
| 176 | } |
| 177 | |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 178 | public ASN1Primitive toASN1Primitive() { |
| 179 | ASN1EncodableVector v = new ASN1EncodableVector(); |
| 180 | v.add(formatVersion); |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 181 | v.add(certificate); |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 182 | v.add(algorithmIdentifier); |
| 183 | v.add(getAuthenticatedAttributes()); |
| 184 | v.add(signature); |
| 185 | return new DERSequence(v); |
| 186 | } |
| 187 | |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 188 | public static int getSignableImageSize(byte[] data) throws Exception { |
| 189 | if (!Arrays.equals(Arrays.copyOfRange(data, 0, 8), |
| 190 | "ANDROID!".getBytes("US-ASCII"))) { |
| 191 | throw new IllegalArgumentException("Invalid image header: missing magic"); |
| 192 | } |
| 193 | |
| 194 | ByteBuffer image = ByteBuffer.wrap(data); |
| 195 | image.order(ByteOrder.LITTLE_ENDIAN); |
| 196 | |
| 197 | image.getLong(); // magic |
| 198 | int kernelSize = image.getInt(); |
| 199 | image.getInt(); // kernel_addr |
| 200 | int ramdskSize = image.getInt(); |
| 201 | image.getInt(); // ramdisk_addr |
| 202 | int secondSize = image.getInt(); |
| 203 | image.getLong(); // second_addr + tags_addr |
| 204 | int pageSize = image.getInt(); |
| 205 | |
| 206 | int length = pageSize // include the page aligned image header |
| 207 | + ((kernelSize + pageSize - 1) / pageSize) * pageSize |
| 208 | + ((ramdskSize + pageSize - 1) / pageSize) * pageSize |
| 209 | + ((secondSize + pageSize - 1) / pageSize) * pageSize; |
| 210 | |
| 211 | length = ((length + pageSize - 1) / pageSize) * pageSize; |
| 212 | |
| 213 | if (length <= 0) { |
| 214 | throw new IllegalArgumentException("Invalid image header: invalid length"); |
| 215 | } |
| 216 | |
| 217 | return length; |
| 218 | } |
| 219 | |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 220 | public static void doSignature( String target, |
| 221 | String imagePath, |
| 222 | String keyPath, |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 223 | String certPath, |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 224 | String outPath) throws Exception { |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 225 | |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 226 | byte[] image = Utils.read(imagePath); |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 227 | int signableSize = getSignableImageSize(image); |
| 228 | |
| 229 | if (signableSize < image.length) { |
| 230 | System.err.println("NOTE: truncating file " + imagePath + |
| 231 | " from " + image.length + " to " + signableSize + " bytes"); |
| 232 | image = Arrays.copyOf(image, signableSize); |
| 233 | } else if (signableSize > image.length) { |
| 234 | throw new IllegalArgumentException("Invalid image: too short, expected " + |
| 235 | signableSize + " bytes"); |
| 236 | } |
| 237 | |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 238 | BootSignature bootsig = new BootSignature(target, image.length); |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 239 | |
| 240 | X509Certificate cert = Utils.loadPEMCertificate(certPath); |
| 241 | bootsig.setCertificate(cert); |
| 242 | |
| 243 | PrivateKey key = Utils.loadDERPrivateKeyFromFile(keyPath); |
| 244 | bootsig.setSignature(bootsig.sign(image, key), |
| 245 | Utils.getSignatureAlgorithmIdentifier(key)); |
| 246 | |
Geremy Condra | d66cefd | 2014-08-14 16:44:31 -0700 | [diff] [blame] | 247 | byte[] encoded_bootsig = bootsig.getEncoded(); |
| 248 | byte[] image_with_metadata = Arrays.copyOf(image, image.length + encoded_bootsig.length); |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 249 | |
| 250 | System.arraycopy(encoded_bootsig, 0, image_with_metadata, |
| 251 | image.length, encoded_bootsig.length); |
| 252 | |
Geremy Condra | d66cefd | 2014-08-14 16:44:31 -0700 | [diff] [blame] | 253 | Utils.write(image_with_metadata, outPath); |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 254 | } |
| 255 | |
Sami Tolvanen | 3380f2f | 2014-10-31 17:18:23 -0700 | [diff] [blame] | 256 | public static void verifySignature(String imagePath) throws Exception { |
| 257 | byte[] image = Utils.read(imagePath); |
| 258 | int signableSize = getSignableImageSize(image); |
| 259 | |
| 260 | if (signableSize >= image.length) { |
| 261 | throw new IllegalArgumentException("Invalid image: not signed"); |
| 262 | } |
| 263 | |
| 264 | byte[] signature = Arrays.copyOfRange(image, signableSize, image.length); |
| 265 | BootSignature bootsig = new BootSignature(signature); |
| 266 | |
| 267 | try { |
| 268 | if (bootsig.verify(Arrays.copyOf(image, signableSize))) { |
| 269 | System.err.println("Signature is VALID"); |
| 270 | System.exit(0); |
| 271 | } else { |
| 272 | System.err.println("Signature is INVALID"); |
| 273 | } |
| 274 | } catch (Exception e) { |
| 275 | e.printStackTrace(System.err); |
| 276 | } |
| 277 | System.exit(1); |
| 278 | } |
| 279 | |
Sami Tolvanen | 40193d9 | 2014-11-14 11:00:18 +0000 | [diff] [blame] | 280 | /* Example usage for signing a boot image using dev keys: |
| 281 | java -cp \ |
| 282 | ../../../out/host/common/obj/JAVA_LIBRARIES/BootSignature_intermediates/ \ |
| 283 | classes/com.android.verity.BootSignature \ |
| 284 | /boot \ |
| 285 | ../../../out/target/product/$PRODUCT/boot.img \ |
| 286 | ../../../build/target/product/security/verity.pk8 \ |
| 287 | ../../../build/target/product/security/verity.x509.pem \ |
| 288 | /tmp/boot.img.signed |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 289 | */ |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 290 | public static void main(String[] args) throws Exception { |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 291 | Security.addProvider(new BouncyCastleProvider()); |
Sami Tolvanen | 3380f2f | 2014-10-31 17:18:23 -0700 | [diff] [blame] | 292 | |
| 293 | if ("-verify".equals(args[0])) { |
Sami Tolvanen | 40193d9 | 2014-11-14 11:00:18 +0000 | [diff] [blame] | 294 | /* args[1] is the path to a signed boot image */ |
Sami Tolvanen | 3380f2f | 2014-10-31 17:18:23 -0700 | [diff] [blame] | 295 | verifySignature(args[1]); |
| 296 | } else { |
Sami Tolvanen | 40193d9 | 2014-11-14 11:00:18 +0000 | [diff] [blame] | 297 | /* args[0] is the target name, typically /boot |
| 298 | args[1] is the path to a boot image to sign |
| 299 | args[2] is the path to a private key |
| 300 | args[3] is the path to the matching public key certificate |
| 301 | args[4] is the path where to output the signed boot image |
| 302 | */ |
Sami Tolvanen | 3380f2f | 2014-10-31 17:18:23 -0700 | [diff] [blame] | 303 | doSignature(args[0], args[1], args[2], args[3], args[4]); |
| 304 | } |
Geremy Condra | cee5bfd | 2014-06-11 13:38:45 -0700 | [diff] [blame] | 305 | } |
Paul Lawrence | 29131b9 | 2014-11-13 22:15:30 +0000 | [diff] [blame] | 306 | } |