1/*
2 * Copyright (C) 2010 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#include <androidfw/ObbFile.h>
18#include <utils/String8.h>
19
20#include <getopt.h>
21#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
24
25using namespace android;
26
27static const char* gProgName = "obbtool";
28static const char* gProgVersion = "1.0";
29
30static int wantUsage = 0;
31static int wantVersion = 0;
32
33#define SALT_LEN 8
34
35#define ADD_OPTS "n:v:os:"
36static const struct option longopts[] = {
37    {"help",       no_argument, &wantUsage,   1},
38    {"version",    no_argument, &wantVersion, 1},
39
40    /* Args for "add" */
41    {"name",       required_argument, NULL, 'n'},
42    {"version",    required_argument, NULL, 'v'},
43    {"overlay",    optional_argument, NULL, 'o'},
44    {"salt",       required_argument, NULL, 's'},
45
46    {NULL, 0, NULL, '\0'}
47};
48
49class PackageInfo {
50public:
51    PackageInfo()
52            : packageName(NULL)
53            , packageVersion(-1)
54            , overlay(false)
55            , salted(false)
56    {
57        memset(&salt, 0, sizeof(salt));
58    }
59
60    char* packageName;
61    int packageVersion;
62    bool overlay;
63    bool salted;
64    unsigned char salt[SALT_LEN];
65};
66
67/*
68 * Print usage info.
69 */
70void usage(void)
71{
72    fprintf(stderr, "Opaque Binary Blob (OBB) Tool\n\n");
73    fprintf(stderr, "Usage:\n");
74    fprintf(stderr,
75        " %s a[dd] [ OPTIONS ] FILENAME\n"
76        "   Adds an OBB signature to the file.\n\n", gProgName);
77    fprintf(stderr,
78        "   Options:\n"
79        "     -n <package name>      sets the OBB package name (required)\n"
80        "     -v <OBB version>       sets the OBB version (required)\n"
81        "     -o                     sets the OBB overlay flag\n"
82        "     -s <8 byte hex salt>   sets the crypto key salt (if encrypted)\n"
83        "\n");
84    fprintf(stderr,
85        " %s r[emove] FILENAME\n"
86        "   Removes the OBB signature from the file.\n\n", gProgName);
87    fprintf(stderr,
88        " %s i[nfo] FILENAME\n"
89        "   Prints the OBB signature information of a file.\n\n", gProgName);
90}
91
92void doAdd(const char* filename, PackageInfo* info) {
93    ObbFile *obb = new ObbFile();
94    if (obb->readFrom(filename)) {
95        fprintf(stderr, "ERROR: %s: OBB signature already present\n", filename);
96        return;
97    }
98
99    obb->setPackageName(String8(info->packageName));
100    obb->setVersion(info->packageVersion);
101    obb->setOverlay(info->overlay);
102    if (info->salted) {
103        obb->setSalt(info->salt, SALT_LEN);
104    }
105
106    if (!obb->writeTo(filename)) {
107        fprintf(stderr, "ERROR: %s: couldn't write OBB signature: %s\n",
108                filename, strerror(errno));
109        return;
110    }
111
112    fprintf(stderr, "OBB signature successfully written\n");
113}
114
115void doRemove(const char* filename) {
116    ObbFile *obb = new ObbFile();
117    if (!obb->readFrom(filename)) {
118        fprintf(stderr, "ERROR: %s: no OBB signature present\n", filename);
119        return;
120    }
121
122    if (!obb->removeFrom(filename)) {
123        fprintf(stderr, "ERROR: %s: couldn't remove OBB signature\n", filename);
124        return;
125    }
126
127    fprintf(stderr, "OBB signature successfully removed\n");
128}
129
130void doInfo(const char* filename) {
131    ObbFile *obb = new ObbFile();
132    if (!obb->readFrom(filename)) {
133        fprintf(stderr, "ERROR: %s: couldn't read OBB signature\n", filename);
134        return;
135    }
136
137    printf("OBB info for '%s':\n", filename);
138    printf("Package name: %s\n", obb->getPackageName().string());
139    printf("     Version: %d\n", obb->getVersion());
140    printf("       Flags: 0x%08x\n", obb->getFlags());
141    printf("     Overlay: %s\n", obb->isOverlay() ? "true" : "false");
142    printf("        Salt: ");
143
144    size_t saltLen;
145    const unsigned char* salt = obb->getSalt(&saltLen);
146    if (salt != NULL) {
147        for (int i = 0; i < SALT_LEN; i++) {
148            printf("%02x", salt[i]);
149        }
150        printf("\n");
151    } else {
152        printf("<empty>\n");
153    }
154}
155
156bool fromHex(char h, unsigned char *b) {
157    if (h >= '0' && h <= '9') {
158        *b = h - '0';
159        return true;
160    } else if (h >= 'a' && h <= 'f') {
161        *b = h - 'a' + 10;
162        return true;
163    } else if (h >= 'A' && h <= 'F') {
164        *b = h - 'A' + 10;
165        return true;
166    }
167    return false;
168}
169
170bool hexToByte(char h1, char h2, unsigned char* b) {
171    unsigned char first, second;
172    if (!fromHex(h1, &first)) return false;
173    if (!fromHex(h2, &second)) return false;
174    *b = (first << 4) | second;
175    return true;
176}
177
178/*
179 * Parse args.
180 */
181int main(int argc, char* const argv[])
182{
183    int opt;
184    int option_index = 0;
185    PackageInfo package_info;
186
187    int result = 1;    // pessimistically assume an error.
188
189    if (argc < 2) {
190        wantUsage = 1;
191        goto bail;
192    }
193
194    while ((opt = getopt_long(argc, argv, ADD_OPTS, longopts, &option_index)) != -1) {
195        switch (opt) {
196        case 0:
197            if (longopts[option_index].flag)
198                break;
199            fprintf(stderr, "'%s' requires an argument\n", longopts[option_index].name);
200            wantUsage = 1;
201            goto bail;
202        case 'n':
203            package_info.packageName = optarg;
204            break;
205        case 'v': {
206            char* end;
207            package_info.packageVersion = strtol(optarg, &end, 10);
208            if (*optarg == '\0' || *end != '\0') {
209                fprintf(stderr, "ERROR: invalid version; should be integer!\n\n");
210                wantUsage = 1;
211                goto bail;
212            }
213            break;
214        }
215        case 'o':
216            package_info.overlay = true;
217            break;
218        case 's':
219            if (strlen(optarg) != SALT_LEN * 2) {
220                fprintf(stderr, "ERROR: salt must be 8 bytes in hex (e.g., ABCD65031337D00D)\n\n");
221                wantUsage = 1;
222                goto bail;
223            }
224
225            package_info.salted = true;
226
227            unsigned char b;
228            for (int i = 0, j = 0; i < SALT_LEN; i++, j+=2) {
229                if (!hexToByte(optarg[j], optarg[j+1], &b)) {
230                    fprintf(stderr, "ERROR: salt must be in hex (e.g., ABCD65031337D00D)\n");
231                    wantUsage = 1;
232                    goto bail;
233                }
234                package_info.salt[i] = b;
235            }
236            break;
237        case '?':
238            wantUsage = 1;
239            goto bail;
240        }
241    }
242
243    if (wantVersion) {
244        fprintf(stderr, "%s %s\n", gProgName, gProgVersion);
245    }
246
247    if (wantUsage) {
248        goto bail;
249    }
250
251#define CHECK_OP(name) \
252    if (strncmp(op, name, opsize)) { \
253        fprintf(stderr, "ERROR: unknown function '%s'!\n\n", op); \
254        wantUsage = 1; \
255        goto bail; \
256    }
257
258    if (optind < argc) {
259        const char* op = argv[optind++];
260        const int opsize = strlen(op);
261
262        if (optind >= argc) {
263            fprintf(stderr, "ERROR: filename required!\n\n");
264            wantUsage = 1;
265            goto bail;
266        }
267
268        const char* filename = argv[optind++];
269
270        switch (op[0]) {
271        case 'a':
272            CHECK_OP("add");
273            if (package_info.packageName == NULL) {
274                fprintf(stderr, "ERROR: arguments required 'packageName' and 'version'\n");
275                goto bail;
276            }
277            doAdd(filename, &package_info);
278            break;
279        case 'r':
280            CHECK_OP("remove");
281            doRemove(filename);
282            break;
283        case 'i':
284            CHECK_OP("info");
285            doInfo(filename);
286            break;
287        default:
288            fprintf(stderr, "ERROR: unknown command '%s'!\n\n", op);
289            wantUsage = 1;
290            goto bail;
291        }
292    }
293
294bail:
295    if (wantUsage) {
296        usage();
297        result = 2;
298    }
299
300    return result;
301}
302