1 /*
2  * Copyright (C) 2021 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 package android.ext.services.displayhash;
18 
19 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM;
20 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_UNKNOWN;
21 
22 import android.graphics.Rect;
23 import android.hardware.HardwareBuffer;
24 import android.os.Build;
25 import android.service.displayhash.DisplayHashParams;
26 import android.service.displayhash.DisplayHashingService;
27 import android.util.ArrayMap;
28 import android.util.Log;
29 import android.view.displayhash.DisplayHash;
30 import android.view.displayhash.DisplayHashResultCallback;
31 import android.view.displayhash.VerifiedDisplayHash;
32 
33 import androidx.annotation.NonNull;
34 import androidx.annotation.RequiresApi;
35 
36 import com.google.common.annotations.VisibleForTesting;
37 
38 import java.util.Map;
39 
40 /**
41  * The implementation service for {@link DisplayHashingService}
42  */
43 @RequiresApi(Build.VERSION_CODES.S)
44 public class DisplayHashingServiceImpl extends DisplayHashingService {
45     static final String TAG = "DisplayHashingService";
46 
47     private static final int INTERVAL_BETWEEN_REQUESTS_MILLIS = 10000;
48 
49     private ImageHashManager mImageHashManager = new ImageHashManager();
50     private final HmacKeyManager mHmacKeyManager = new HmacKeyManager();
51     private final ArrayMap<String, DisplayHashParams> mDisplayHashParams = new ArrayMap<>();
52 
53     @Override
onCreate()54     public void onCreate() {
55         super.onCreate();
56         mDisplayHashParams.put("PHASH", new DisplayHashParams.Builder()
57                 .setBufferSize(32, 32)
58                 .setGrayscaleBuffer(true).build());
59     }
60 
61     @Override
onGenerateDisplayHash(@onNull byte[] salt, @NonNull HardwareBuffer buffer, @NonNull Rect bounds, @NonNull String hashAlgorithm, @NonNull DisplayHashResultCallback callback)62     public void onGenerateDisplayHash(@NonNull byte[] salt,
63             @NonNull HardwareBuffer buffer, @NonNull Rect bounds,
64             @NonNull String hashAlgorithm, @NonNull DisplayHashResultCallback callback) {
65         if (salt == null) {
66             Log.w(TAG, "Failed to generate display hash: salt was null");
67             callback.onDisplayHashError(DISPLAY_HASH_ERROR_UNKNOWN);
68             return;
69         }
70 
71         int hashAlgorithmIndex = getIndexForHashAlgorithm(hashAlgorithm);
72         if (hashAlgorithmIndex < 0) {
73             Log.w(TAG, "Failed to generate display hash: invalid hash request");
74             callback.onDisplayHashError(DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM);
75             return;
76         }
77 
78         if (buffer == null) {
79             Log.w(TAG, "Failed to generate display hash: null buffer");
80             callback.onDisplayHashError(DISPLAY_HASH_ERROR_UNKNOWN);
81             return;
82         }
83 
84         long timestamp = System.currentTimeMillis();
85         byte[] imageHash = mImageHashManager.generateHash(buffer, hashAlgorithm);
86         if (imageHash == null) {
87             Log.w(TAG, "Failed to generate display hash: failed to create image hash");
88             callback.onDisplayHashError(DISPLAY_HASH_ERROR_UNKNOWN);
89             return;
90         }
91 
92         byte[] hmac = mHmacKeyManager.generateHmac(salt, timestamp, bounds, hashAlgorithmIndex,
93                 imageHash);
94         callback.onDisplayHashResult(
95                 new DisplayHash(timestamp, bounds, hashAlgorithm, imageHash, hmac));
96     }
97 
98     @Override
onVerifyDisplayHash(@onNull byte[] salt, @NonNull DisplayHash displayHash)99     public VerifiedDisplayHash onVerifyDisplayHash(@NonNull byte[] salt,
100             @NonNull DisplayHash displayHash) {
101         if (displayHash == null || salt == null) {
102             Log.w(TAG, "Failed to verify display hash: invalid token or salt");
103             return null;
104         }
105 
106         boolean verified = mHmacKeyManager.verifyHmac(salt,
107                 displayHash.getTimeMillis(), displayHash.getBoundsInWindow(),
108                 getIndexForHashAlgorithm(displayHash.getHashAlgorithm()),
109                 displayHash.getImageHash(), displayHash.getHmac());
110 
111         if (verified) {
112             return new VerifiedDisplayHash(displayHash.getTimeMillis(),
113                     displayHash.getBoundsInWindow(), displayHash.getHashAlgorithm(),
114                     displayHash.getImageHash());
115         } else {
116             return null;
117         }
118     }
119 
120     @Override
onGetIntervalBetweenRequestsMillis()121     public int onGetIntervalBetweenRequestsMillis() {
122         return INTERVAL_BETWEEN_REQUESTS_MILLIS;
123     }
124 
getIndexForHashAlgorithm(String hashAlgorithm)125     private int getIndexForHashAlgorithm(String hashAlgorithm) {
126         return mDisplayHashParams.indexOfKey(hashAlgorithm);
127     }
128 
129     @VisibleForTesting
setImageHashManager(ImageHashManager imageHashManager)130     public void setImageHashManager(ImageHashManager imageHashManager) {
131         mImageHashManager = imageHashManager;
132     }
133 
134     @NonNull
135     @Override
onGetDisplayHashAlgorithms()136     public Map<String, DisplayHashParams> onGetDisplayHashAlgorithms() {
137         return mDisplayHashParams;
138     }
139 }
140