| /* |
| * 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.Security; |
| import java.security.Signature; |
| import java.security.cert.X509Certificate; |
| import java.util.Enumeration; |
| import org.bouncycastle.asn1.ASN1Encodable; |
| import org.bouncycastle.asn1.ASN1EncodableVector; |
| import org.bouncycastle.asn1.ASN1InputStream; |
| import org.bouncycastle.asn1.ASN1Integer; |
| import org.bouncycastle.asn1.ASN1Object; |
| import org.bouncycastle.asn1.ASN1Primitive; |
| import org.bouncycastle.asn1.ASN1Sequence; |
| 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; |
| import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| |
| /** |
| * 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 = Utils.getSignatureAlgorithmIdentifier(key); |
| } |
| |
| 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; |
| private X509Certificate certificate; |
| |
| private static final int FORMAT_VERSION = 0; |
| |
| public BootKeystore() { |
| this.formatVersion = new ASN1Integer(FORMAT_VERSION); |
| 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 void setCertificate(X509Certificate cert) { |
| certificate = cert; |
| } |
| |
| 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 parse(byte[] input) throws Exception { |
| ASN1InputStream stream = new ASN1InputStream(input); |
| ASN1Sequence sequence = (ASN1Sequence) stream.readObject(); |
| |
| formatVersion = (ASN1Integer) sequence.getObjectAt(0); |
| if (formatVersion.getValue().intValue() != FORMAT_VERSION) { |
| throw new IllegalArgumentException("Unsupported format version"); |
| } |
| |
| ASN1Sequence keys = (ASN1Sequence) sequence.getObjectAt(1); |
| Enumeration e = keys.getObjects(); |
| while (e.hasMoreElements()) { |
| keyBag.add((ASN1Encodable) e.nextElement()); |
| } |
| |
| ASN1Object sig = sequence.getObjectAt(2).toASN1Primitive(); |
| signature = new BootSignature(sig.getEncoded()); |
| } |
| |
| public boolean verify() throws Exception { |
| byte[] innerKeystore = getInnerKeystore(); |
| return Utils.verify(signature.getPublicKey(), innerKeystore, |
| signature.getSignature(), signature.getAlgorithmIdentifier()); |
| } |
| |
| public void sign(PrivateKey privateKey) throws Exception { |
| byte[] innerKeystore = getInnerKeystore(); |
| byte[] rawSignature = Utils.sign(privateKey, innerKeystore); |
| signature = new BootSignature("keystore", innerKeystore.length); |
| signature.setCertificate(certificate); |
| signature.setSignature(rawSignature, |
| Utils.getSignatureAlgorithmIdentifier(privateKey)); |
| } |
| |
| public void dump() throws Exception { |
| System.out.println(ASN1Dump.dumpAsString(toASN1Primitive())); |
| } |
| |
| private static void usage() { |
| System.err.println("usage: KeystoreSigner <privatekey.pk8> " + |
| "<certificate.x509.pem> <outfile> <publickey0.der> " + |
| "... <publickeyN-1.der> | -verify <keystore>"); |
| System.exit(1); |
| } |
| |
| public static void main(String[] args) throws Exception { |
| if (args.length < 2) { |
| usage(); |
| return; |
| } |
| |
| Security.addProvider(new BouncyCastleProvider()); |
| BootKeystore ks = new BootKeystore(); |
| |
| if ("-verify".equals(args[0])) { |
| ks.parse(Utils.read(args[1])); |
| |
| try { |
| if (ks.verify()) { |
| System.err.println("Signature is VALID"); |
| System.exit(0); |
| } else { |
| System.err.println("Signature is INVALID"); |
| } |
| } catch (Exception e) { |
| e.printStackTrace(System.err); |
| } |
| System.exit(1); |
| } else { |
| String privkeyFname = args[0]; |
| String certFname = args[1]; |
| String outfileFname = args[2]; |
| |
| ks.setCertificate(Utils.loadPEMCertificate(certFname)); |
| |
| for (int i = 3; i < args.length; i++) { |
| ks.addPublicKey(Utils.read(args[i])); |
| } |
| |
| ks.sign(Utils.loadDERPrivateKeyFromFile(privkeyFname)); |
| Utils.write(ks.getEncoded(), outfileFname); |
| } |
| } |
| } |