208 lines
6.9 KiB
Java
208 lines
6.9 KiB
Java
|
/*
|
||
|
* 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);
|
||
|
}
|
||
|
}
|
||
|
}
|