001/* 002 * Copyright 2016 The AppAuth for Android Authors. All Rights Reserved. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 005 * in compliance with the License. You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software distributed under the 010 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 011 * express or implied. See the License for the specific language governing permissions and 012 * limitations under the License. 013 */ 014 015package net.openid.appauth.browser; 016 017import android.content.pm.PackageInfo; 018import android.content.pm.Signature; 019import android.util.Base64; 020import androidx.annotation.NonNull; 021 022import java.security.MessageDigest; 023import java.security.NoSuchAlgorithmException; 024import java.util.HashSet; 025import java.util.Set; 026 027/** 028 * Represents a browser that may be used for an authorization flow. 029 */ 030public class BrowserDescriptor { 031 032 // See: http://stackoverflow.com/a/2816747 033 private static final int PRIME_HASH_FACTOR = 92821; 034 035 private static final String DIGEST_SHA_512 = "SHA-512"; 036 037 /** 038 * The package name of the browser app. 039 */ 040 public final String packageName; 041 042 /** 043 * The set of {@link android.content.pm.Signature signatures} of the browser app, 044 * which have been hashed with SHA-512, and Base-64 URL-safe encoded. 045 */ 046 public final Set<String> signatureHashes; 047 048 /** 049 * The version string of the browser app. 050 */ 051 public final String version; 052 053 /** 054 * Whether it is intended that the browser will be used via a custom tab. 055 */ 056 public final Boolean useCustomTab; 057 058 /** 059 * Creates a description of a browser from a {@link PackageInfo} object returned from the 060 * {@link android.content.pm.PackageManager}. The object is expected to include the 061 * signatures of the app, which can be retrieved with the 062 * {@link android.content.pm.PackageManager#GET_SIGNATURES GET_SIGNATURES} flag when 063 * calling {@link android.content.pm.PackageManager#getPackageInfo(String, int)}. 064 */ 065 public BrowserDescriptor(@NonNull PackageInfo packageInfo, boolean useCustomTab) { 066 this( 067 packageInfo.packageName, 068 generateSignatureHashes(packageInfo.signatures), 069 packageInfo.versionName, 070 useCustomTab); 071 } 072 073 /** 074 * Creates a description of a browser from the core properties that are frequently used to 075 * decide whether a browser can be used for an authorization flow. In most cases, it is 076 * more convenient to use the other variant of the constructor that consumes a 077 * {@link PackageInfo} object provided by the package manager. 078 * 079 * @param packageName 080 * The Android package name of the browser. 081 * @param signatureHashes 082 * The set of SHA-512, Base64 url safe encoded signatures for the app. This can be 083 * generated for a signature by calling {@link #generateSignatureHash(Signature)}. 084 * @param version 085 * The version name of the browser. 086 * @param useCustomTab 087 * Whether it is intended to use the browser as a custom tab. 088 */ 089 public BrowserDescriptor( 090 @NonNull String packageName, 091 @NonNull Set<String> signatureHashes, 092 @NonNull String version, 093 boolean useCustomTab) { 094 this.packageName = packageName; 095 this.signatureHashes = signatureHashes; 096 this.version = version; 097 this.useCustomTab = useCustomTab; 098 } 099 100 /** 101 * Creates a copy of this browser descriptor, changing the intention to use it as a custom 102 * tab to the specified value. 103 */ 104 @NonNull 105 public BrowserDescriptor changeUseCustomTab(boolean newUseCustomTabValue) { 106 return new BrowserDescriptor( 107 packageName, 108 signatureHashes, 109 version, 110 newUseCustomTabValue); 111 } 112 113 @Override 114 public boolean equals(Object obj) { 115 if (this == obj) { 116 return true; 117 } 118 119 if (obj == null || !(obj instanceof BrowserDescriptor)) { 120 return false; 121 } 122 123 BrowserDescriptor other = (BrowserDescriptor) obj; 124 return this.packageName.equals(other.packageName) 125 && this.version.equals(other.version) 126 && this.useCustomTab == other.useCustomTab 127 && this.signatureHashes.equals(other.signatureHashes); 128 } 129 130 @Override 131 public int hashCode() { 132 int hash = packageName.hashCode(); 133 134 hash = PRIME_HASH_FACTOR * hash + version.hashCode(); 135 hash = PRIME_HASH_FACTOR * hash + (useCustomTab ? 1 : 0); 136 137 for (String signatureHash : signatureHashes) { 138 hash = PRIME_HASH_FACTOR * hash + signatureHash.hashCode(); 139 } 140 141 return hash; 142 } 143 144 /** 145 * Generates a SHA-512 hash, Base64 url-safe encoded, from a {@link Signature}. 146 */ 147 @NonNull 148 public static String generateSignatureHash(@NonNull Signature signature) { 149 try { 150 MessageDigest digest = MessageDigest.getInstance(DIGEST_SHA_512); 151 byte[] hashBytes = digest.digest(signature.toByteArray()); 152 return Base64.encodeToString(hashBytes, Base64.URL_SAFE | Base64.NO_WRAP); 153 } catch (NoSuchAlgorithmException e) { 154 throw new IllegalStateException( 155 "Platform does not support" + DIGEST_SHA_512 + " hashing"); 156 } 157 } 158 159 /** 160 * Generates a set of SHA-512, Base64 url-safe encoded signature hashes from the provided 161 * array of signatures. 162 */ 163 @NonNull 164 public static Set<String> generateSignatureHashes(@NonNull Signature[] signatures) { 165 Set<String> signatureHashes = new HashSet<>(); 166 for (Signature signature : signatures) { 167 signatureHashes.add(generateSignatureHash(signature)); 168 } 169 170 return signatureHashes; 171 } 172}