blob: 56db32a3071d5cbf2687ff65003ddcd911e48b62 [file] [log] [blame]
Mathew Inwood96c419f2018-12-04 11:52:42 +00001/*
2 * Copyright (C) 2018 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
17package com.android.server.signedconfig;
18
19import android.os.Build;
20import android.util.Slog;
Mathew Inwoodb375be52019-01-04 11:36:25 +000021import android.util.StatsLog;
Mathew Inwood96c419f2018-12-04 11:52:42 +000022
23import java.nio.charset.StandardCharsets;
24import java.security.InvalidKeyException;
25import java.security.KeyFactory;
26import java.security.NoSuchAlgorithmException;
27import java.security.PublicKey;
28import java.security.Signature;
29import java.security.SignatureException;
30import java.security.spec.EncodedKeySpec;
31import java.security.spec.InvalidKeySpecException;
32import java.security.spec.X509EncodedKeySpec;
33import java.util.Base64;
34
35/**
36 * Helper class for verifying config signatures.
37 */
38public class SignatureVerifier {
39
40 private static final String TAG = "SignedConfig";
41 private static final boolean DBG = false;
42
43 private static final String DEBUG_KEY =
44 "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaAn2XVifsLTHg616nTsOMVmlhBoECGbTEBTKKvdd2hO60"
45 + "pj1pnU8SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ==";
Mathew Inwood45942512018-12-14 13:53:52 +000046 private static final String PROD_KEY =
47 "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+lky6wKyGL6lE1VrD0YTMHwb0Xwc+tzC8MvnrzVxodvTp"
48 + "VY/jV7V+Zktcx+pry43XPABFRXtbhTo+qykhyBA1g==";
Mathew Inwood96c419f2018-12-04 11:52:42 +000049
Mathew Inwoodb375be52019-01-04 11:36:25 +000050 private final SignedConfigEvent mEvent;
Mathew Inwood96c419f2018-12-04 11:52:42 +000051 private final PublicKey mDebugKey;
Mathew Inwood45942512018-12-14 13:53:52 +000052 private final PublicKey mProdKey;
Mathew Inwood96c419f2018-12-04 11:52:42 +000053
Mathew Inwoodb375be52019-01-04 11:36:25 +000054 public SignatureVerifier(SignedConfigEvent event) {
55 mEvent = event;
Mathew Inwood45942512018-12-14 13:53:52 +000056 mDebugKey = Build.IS_DEBUGGABLE ? createKey(DEBUG_KEY) : null;
57 mProdKey = createKey(PROD_KEY);
Mathew Inwood96c419f2018-12-04 11:52:42 +000058 }
59
60 private static PublicKey createKey(String base64) {
61 EncodedKeySpec keySpec;
62 try {
63 byte[] key = Base64.getDecoder().decode(base64);
64 keySpec = new X509EncodedKeySpec(key);
65 } catch (IllegalArgumentException e) {
66 Slog.e(TAG, "Failed to base64 decode public key", e);
67 return null;
68 }
69 try {
70 KeyFactory factory = KeyFactory.getInstance("EC");
71 return factory.generatePublic(keySpec);
72 } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
73 Slog.e(TAG, "Failed to construct public key", e);
74 return null;
75 }
76 }
77
Mathew Inwood45942512018-12-14 13:53:52 +000078 private boolean verifyWithPublicKey(PublicKey key, byte[] data, byte[] signature)
79 throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
80 Signature verifier = Signature.getInstance("SHA256withECDSA");
81 verifier.initVerify(key);
82 verifier.update(data);
83 return verifier.verify(signature);
84 }
85
Mathew Inwood96c419f2018-12-04 11:52:42 +000086 /**
87 * Verify a signature for signed config.
88 *
89 * @param config Config as read from APK meta-data.
90 * @param base64Signature Signature as read from APK meta-data.
91 * @return {@code true} iff the signature was successfully verified.
92 */
93 public boolean verifySignature(String config, String base64Signature)
94 throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
95 byte[] signature;
96 try {
97 signature = Base64.getDecoder().decode(base64Signature);
98 } catch (IllegalArgumentException e) {
Mathew Inwoodb375be52019-01-04 11:36:25 +000099 mEvent.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__BASE64_FAILURE_SIGNATURE;
Mathew Inwood96c419f2018-12-04 11:52:42 +0000100 Slog.e(TAG, "Failed to base64 decode signature");
101 return false;
102 }
103 byte[] data = config.getBytes(StandardCharsets.UTF_8);
104 if (DBG) Slog.i(TAG, "Data: " + Base64.getEncoder().encodeToString(data));
105
106 if (Build.IS_DEBUGGABLE) {
107 if (mDebugKey != null) {
108 if (DBG) Slog.w(TAG, "Trying to verify signature using debug key");
Mathew Inwood45942512018-12-14 13:53:52 +0000109 if (verifyWithPublicKey(mDebugKey, data, signature)) {
Mathew Inwood96c419f2018-12-04 11:52:42 +0000110 Slog.i(TAG, "Verified config using debug key");
Mathew Inwoodb375be52019-01-04 11:36:25 +0000111 mEvent.verifiedWith = StatsLog.SIGNED_CONFIG_REPORTED__VERIFIED_WITH__DEBUG;
Mathew Inwood96c419f2018-12-04 11:52:42 +0000112 return true;
113 } else {
114 if (DBG) Slog.i(TAG, "Config verification failed using debug key");
115 }
116 } else {
117 Slog.w(TAG, "Debuggable build, but have no debug key");
118 }
119 }
Mathew Inwood45942512018-12-14 13:53:52 +0000120 if (mProdKey == null) {
121 Slog.e(TAG, "No prod key; construction failed?");
Mathew Inwood610d0962019-01-15 11:50:28 +0000122 mEvent.status =
123 StatsLog.SIGNED_CONFIG_REPORTED__STATUS__SIGNATURE_CHECK_FAILED_PROD_KEY_ABSENT;
Mathew Inwood45942512018-12-14 13:53:52 +0000124 return false;
125 }
126 if (verifyWithPublicKey(mProdKey, data, signature)) {
127 Slog.i(TAG, "Verified config using production key");
128 mEvent.verifiedWith = StatsLog.SIGNED_CONFIG_REPORTED__VERIFIED_WITH__PRODUCTION;
129 return true;
130 } else {
131 if (DBG) Slog.i(TAG, "Verification failed using production key");
132 mEvent.status = StatsLog.SIGNED_CONFIG_REPORTED__STATUS__SIGNATURE_CHECK_FAILED;
133 return false;
134 }
Mathew Inwood96c419f2018-12-04 11:52:42 +0000135 }
136}