Fastbootd: flashing certification

Change-Id: I564086908942463eebee69449d484ba03effc50e
This commit is contained in:
Szymon Starzycki 2013-08-19 16:42:25 -07:00
parent bc7cbfd4e3
commit 5ccecfd26d
10 changed files with 500 additions and 8 deletions

View File

@ -30,6 +30,7 @@ LOCAL_SRC_FILES := \
commands/virtual_partitions.c \
fastbootd.c \
protocol.c \
secure.c \
transport.c \
trigger.c \
usb_linux_client.c \
@ -37,11 +38,12 @@ LOCAL_SRC_FILES := \
LOCAL_MODULE := fastbootd
LOCAL_MODULE_TAGS := optional
LOCAL_CFLAGS := -Wall -Werror -Wno-unused-parameter
LOCAL_CFLAGS := -Wall -Werror -Wno-unused-parameter -DFLASH_CERT
LOCAL_LDFLAGS := -ldl
LOCAL_SHARED_LIBRARIES := \
libhardware \
libcrypto \
libhardware_legacy
LOCAL_STATIC_LIBRARIES := \

View File

@ -52,6 +52,8 @@
static void cmd_boot(struct protocol_handle *phandle, const char *arg)
{
int sz, atags_sz, new_atags_sz;
int rv;
unsigned kernel_actual;
unsigned ramdisk_actual;
unsigned second_actual;
@ -59,9 +61,11 @@ static void cmd_boot(struct protocol_handle *phandle, const char *arg)
void *ramdisk_ptr;
void *second_ptr;
struct boot_img_hdr *hdr;
int sz, atags_sz, new_atags_sz;
int rv;
char *ptr = NULL, *atags_ptr = NULL, *new_atags = NULL;
char *ptr = NULL;
char *atags_ptr = NULL;
char *new_atags = NULL;
int data_fd = 0;
D(DEBUG, "cmd_boot %s\n", arg);
if (phandle->download_fd < 0) {
@ -75,9 +79,18 @@ static void cmd_boot(struct protocol_handle *phandle, const char *arg)
goto error;
}
sz = get_file_size(phandle->download_fd);
// TODO: With cms we can also verify partition name included as
// cms signed attribute
if (flash_validate_certificate(phandle->download_fd, &data_fd) != 1) {
fastboot_fail(phandle, "Access forbiden you need the certificate");
return;
}
sz = get_file_size(data_fd);
ptr = (char *) mmap(NULL, sz, PROT_READ,
MAP_POPULATE | MAP_PRIVATE, phandle->download_fd, 0);
MAP_POPULATE | MAP_PRIVATE, data_fd, 0);
hdr = (struct boot_img_hdr *) ptr;
if (ptr == MAP_FAILED) {
@ -130,6 +143,7 @@ static void cmd_boot(struct protocol_handle *phandle, const char *arg)
free(atags_ptr);
munmap(ptr, sz);
free(new_atags);
close(data_fd);
D(INFO, "Kexec going to reboot");
reboot(LINUX_REBOOT_CMD_KEXEC);
@ -256,6 +270,7 @@ static void cmd_flash(struct protocol_handle *phandle, const char *arg)
char data[BOOT_MAGIC_SIZE];
char path[PATH_MAX];
ssize_t header_sz = 0;
int data_fd = 0;
D(DEBUG, "cmd_flash %s\n", arg);
@ -273,9 +288,14 @@ static void cmd_flash(struct protocol_handle *phandle, const char *arg)
return;
}
if (flash_validate_certificate(phandle->download_fd, &data_fd) != 1) {
fastboot_fail(phandle, "Access forbiden you need certificate");
return;
}
// TODO: Maybe its goot idea to check whether the partition is just bootable partition
if (!strcmp(arg, "boot") || !strcmp(arg, "recovery")) {
if (read_data_once(phandle->download_fd, data, BOOT_MAGIC_SIZE) < BOOT_MAGIC_SIZE) {
if (read_data_once(data_fd, data, BOOT_MAGIC_SIZE) < BOOT_MAGIC_SIZE) {
fastboot_fail(phandle, "incoming data read error, cannot read boot header");
return;
}
@ -287,7 +307,10 @@ static void cmd_flash(struct protocol_handle *phandle, const char *arg)
partition = flash_get_partiton(path);
sz = get_file_size64(phandle->download_fd);
sz = get_file_size64(data_fd);
sz -= header_sz;
if (sz > get_file_size64(partition)) {
flash_close(partition);
D(WARN, "size of file too large");
@ -304,6 +327,8 @@ static void cmd_flash(struct protocol_handle *phandle, const char *arg)
D(INFO, "partition '%s' updated\n", arg);
flash_close(partition);
close(data_fd);
//TODO: check who is closing phandle->download_fd
fastboot_okay(phandle, "");
}

View File

@ -39,6 +39,9 @@
#include "utils.h"
#include "commands/partitions.h"
#ifdef FLASH_CERT
#include "secure.h"
#endif
#define ALLOWED_CHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-."
#define BUFFER_SIZE 1024 * 1024
@ -112,3 +115,47 @@ int flash_write(int partition_fd, int data_fd, ssize_t size, ssize_t skip)
return 0;
}
#ifdef FLASH_CERT
int flash_validate_certificate(int signed_fd, int *data_fd) {
int ret = 0;
const char *cert_path;
X509_STORE *store = NULL;
CMS_ContentInfo *content_info;
BIO *content;
cert_path = fastboot_getvar("certificate-path");
if (!strcmp(cert_path, "")) {
D(ERR, "could not find cert-key value in config file");
goto finish;
}
store = cert_store_from_path(cert_path);
if (store == NULL) {
D(ERR, "unable to create certification store");
goto finish;
}
if (cert_read(signed_fd, &content_info, &content)) {
D(ERR, "reading data failed");
goto finish;
}
ret = cert_verify(content, content_info, store, data_fd);
cert_release(content, content_info);
return ret;
finish:
if (store != NULL)
cert_release_store(store);
return ret;
}
#else
int flash_validate_certificate(int signed_fd, int *data_fd) {
return 1;
}
#endif

View File

@ -58,5 +58,7 @@ static inline ssize_t read_data_once(int fd, char *buffer, ssize_t size) {
return readcount;
}
int flash_validate_certificate(int signed_fd, int *data_fd);
#endif

View File

@ -21,6 +21,7 @@
#include "debug.h"
#include "trigger.h"
#include "secure.h"
unsigned int debug_level = DEBUG;
@ -36,6 +37,7 @@ int main(int argc, char **argv)
klog_init();
klog_set_level(6);
cert_init_crypto();
config_init();
load_trigger();
commands_init();

View File

@ -0,0 +1,181 @@
package signtool;
import java.io.*;
import java.util.Properties;
import java.util.ArrayList;
import javax.mail.internet.*;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.activation.MailcapCommandMap;
import javax.activation.CommandMap;
import java.security.PrivateKey;
import java.security.Security;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateEncodingException;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedGenerator;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSSignedGenerator;
import org.bouncycastle.cms.CMSProcessable;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.util.Store;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.ASN1Object;
public class SignImg {
/* It reads private key in pkcs#8 formate
* Conversion:
* openssl pkcs8 -topk8 -nocrypt -outform DER < inkey.pem > outkey.pk8
*/
private static PrivateKey getPrivateKey(String path) throws IOException, FileNotFoundException, NoSuchAlgorithmException, InvalidKeySpecException {
File file = new File(path);
FileInputStream fis = new FileInputStream(file);
byte[] data = new byte[(int)file.length()];
fis.read(data);
fis.close();
PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(data);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privateKey = kf.generatePrivate(kspec);
return privateKey;
}
private static MimeBodyPart getContent(String path) throws IOException, FileNotFoundException, MessagingException {
MimeBodyPart body = new MimeBodyPart();
File file = new File(path);
FileInputStream fis = new FileInputStream(file);
byte[] data = new byte[(int)file.length()];
fis.read(data);
fis.close();
body.setContent(data, "application/octet-stream");
return body;
}
private static CMSProcessableByteArray getCMSContent(String path) throws IOException, FileNotFoundException, MessagingException {
File file = new File(path);
FileInputStream fis = new FileInputStream(file);
byte[] data = new byte[(int)file.length()];
fis.read(data);
fis.close();
CMSProcessableByteArray cms = new CMSProcessableByteArray(data);
return cms;
}
private static X509Certificate readCert(String path) throws IOException, FileNotFoundException, CertificateException {
File file = new File(path);
FileInputStream is = new FileInputStream(file);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(is);
is.close();
return (X509Certificate) cert;
}
private static void save(MimeBodyPart content, String path) throws IOException, FileNotFoundException, MessagingException {
File file = new File(path);
FileOutputStream os = new FileOutputStream(file);
content.writeTo(os);
os.close();
}
private static Store certToStore(X509Certificate certificate) throws CertificateEncodingException {
ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
certList.add(certificate);
return new JcaCertStore(certList);
}
public static void setDefaultMailcap()
{
MailcapCommandMap _mailcap =
(MailcapCommandMap)CommandMap.getDefaultCommandMap();
_mailcap.addMailcap("application/pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_signature");
_mailcap.addMailcap("application/pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_mime");
_mailcap.addMailcap("application/x-pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_signature");
_mailcap.addMailcap("application/x-pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime");
_mailcap.addMailcap("multipart/signed;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.multipart_signed");
CommandMap.setDefaultCommandMap(_mailcap);
}
public static void main(String[] args) {
try {
if (args.length < 4) {
System.out.println("Usage: signimg data private_key certificate output");
return;
}
System.out.println("Signing the image");
setDefaultMailcap();
Security.addProvider(new BouncyCastleProvider());
PrivateKey key = getPrivateKey(args[1]);
System.out.println("File read sucessfully");
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
CMSTypedData body = getCMSContent(args[0]);
System.out.println("Content read sucessfully");
X509Certificate cert = (X509Certificate) readCert(args[2]);
System.out.println("Certificate read sucessfully");
ContentSigner sha256Signer = new JcaContentSignerBuilder("SHA256withRSA").setProvider("BC").build(key);
Store certs = certToStore(cert);
generator.addCertificates(certs);
generator.addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().setProvider("BC").build())
.build(sha256Signer, cert));
CMSSignedData signed = generator.generate(body, true);
System.out.println("Signed");
Properties props = System.getProperties();
Session session = Session.getDefaultInstance(props, null);
File file = new File(args[3]);
FileOutputStream os = new FileOutputStream(file);
ASN1InputStream asn1 = new ASN1InputStream(signed.getEncoded());
ByteArrayOutputStream out = new ByteArrayOutputStream();
DEROutputStream dOut = new DEROutputStream(os);
dOut.writeObject(ASN1Object.fromByteArray(signed.getEncoded()));
}
catch (Exception ex) {
System.out.println("Exception during programm execution: " + ex.getMessage());
}
}
}

166
fastbootd/secure.c Normal file
View File

@ -0,0 +1,166 @@
/*
* Copyright (c) 2009-2013, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google, Inc. nor the names of its contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <openssl/pem.h>
#include <openssl/engine.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/pem.h>
#include <openssl/cms.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "secure.h"
#include "debug.h"
#include "utils.h"
void cert_init_crypto() {
CRYPTO_malloc_init();
ERR_load_crypto_strings();
OpenSSL_add_all_algorithms();
ENGINE_load_builtin_engines();
}
X509_STORE *cert_store_from_path(const char *path) {
X509_STORE *store;
struct stat st;
X509_LOOKUP *lookup;
if (stat(path, &st)) {
D(ERR, "Unable to stat cert path");
goto error;
}
if (!(store = X509_STORE_new())) {
goto error;
}
if (S_ISDIR(st.st_mode)) {
lookup = X509_STORE_add_lookup(store,X509_LOOKUP_hash_dir());
if (lookup == NULL)
goto error;
if (!X509_LOOKUP_add_dir(lookup, path, X509_FILETYPE_PEM)) {
D(ERR, "Error loading cert directory %s", path);
goto error;
}
}
else if(S_ISREG(st.st_mode)) {
lookup = X509_STORE_add_lookup(store,X509_LOOKUP_file());
if (lookup == NULL)
goto error;
if (!X509_LOOKUP_load_file(lookup, path, X509_FILETYPE_PEM)) {
D(ERR, "Error loading cert directory %s", path);
goto error;
}
}
else {
D(ERR, "cert path is not directory or regular file");
goto error;
}
return store;
error:
return NULL;
}
int cert_read(int fd, CMS_ContentInfo **content, BIO **output) {
BIO *input;
*output = NULL;
input = BIO_new_fd(fd, BIO_NOCLOSE);
if (input == NULL) {
D(ERR, "Unable to open input");
goto error;
}
*content = SMIME_read_CMS(input, output);
if (*content == NULL) {
unsigned long err = ERR_peek_last_error();
D(ERR, "Unable to parse input file: %s", ERR_lib_error_string(err));
goto error_read;
}
BIO_free(input);
return 0;
error_read:
BIO_free(input);
error:
return 1;
}
int cert_verify(BIO *content, CMS_ContentInfo *content_info, X509_STORE *store, int *out_fd) {
BIO *output_temp;
int ret;
*out_fd = create_temp_file();
if (*out_fd < 0) {
D(ERR, "unable to create temporary file");
return -1;
}
output_temp = BIO_new_fd(*out_fd, BIO_NOCLOSE);
if (output_temp == NULL) {
D(ERR, "unable to create temporary bio");
close(*out_fd);
return -1;
}
ret = CMS_verify(content_info, NULL ,store, content, output_temp, 0);
if (ret == 0) {
char buf[256];
unsigned long err = ERR_peek_last_error();
D(ERR, "Verification failed with reason: %s, %s", ERR_lib_error_string(err), ERR_error_string(err, buf));
D(ERR, "Data used: content %d", (int) content);
}
ERR_clear_error();
ERR_remove_state(0);
BIO_free(output_temp);
return ret;
}
void cert_release(BIO *content, CMS_ContentInfo *info) {
BIO_free(content);
CMS_ContentInfo_free(info);
}

53
fastbootd/secure.h Normal file
View File

@ -0,0 +1,53 @@
/*
* Copyright (c) 2009-2013, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google, Inc. nor the names of its contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef _FASTBOOTD_SECURE_H
#define _FASTBOOTD_SECURE_H
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/pem.h>
#include <openssl/cms.h>
void cert_init_crypto();
X509_STORE *cert_store_from_path(const char*stream);
static inline void cert_release_store(X509_STORE *store) {
X509_STORE_free(store);
}
int cert_read(int fd, CMS_ContentInfo **content, BIO **output);
int cert_verify(BIO *content, CMS_ContentInfo *content_info, X509_STORE *store, int *out_fd);
void cert_release(BIO *content, CMS_ContentInfo *info);
#endif

View File

@ -34,6 +34,7 @@
#include <stdio.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <stdlib.h>
#include "utils.h"
#include "debug.h"
@ -145,3 +146,15 @@ int wipe_block_device(int fd, int64_t len)
return 0;
}
int create_temp_file() {
char tempname[] = "/dev/fastboot_data_XXXXXX";
int fd;
fd = mkstemp(tempname);
if (fd < 0)
return -1;
unlink(tempname);
return fd;
}

View File

@ -42,6 +42,7 @@ uint64_t get_file_size64(int fd);
uint64_t get_file_size(int fd);
uint64_t get_block_device_size(int fd);
int wipe_block_device(int fd, int64_t len);
int create_temp_file();
#define ROUND_TO_PAGE(address,pagesize) ((address + pagesize - 1) & (~(pagesize - 1)))