1 // Copyright 2021 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 //////////////////////////////////////////////////////////////////////////////// 16 17 package com.google.crypto.tink.internal; 18 19 import com.google.crypto.tink.SecretKeyAccess; 20 import com.google.crypto.tink.util.Bytes; 21 import com.google.crypto.tink.util.SecretBytes; 22 import java.io.IOException; 23 import java.io.InputStream; 24 import java.nio.charset.Charset; 25 import java.security.GeneralSecurityException; 26 import java.security.SecureRandom; 27 import java.util.Objects; 28 import javax.annotation.Nullable; 29 30 /** Helper functions used throughout Tink, for Tink internal use only. */ 31 public final class Util { 32 /** Android 18-compatible alternative to {@link java.nio.charset.StandardCharsets#UTF_8}. */ 33 public static final Charset UTF_8 = Charset.forName("UTF-8"); 34 35 /** Returns a positive random int which can be used as a key ID in a keyset. */ randKeyId()36 public static int randKeyId() { 37 SecureRandom secureRandom = new SecureRandom(); 38 byte[] rand = new byte[4]; 39 int result = 0; 40 while (result == 0) { 41 secureRandom.nextBytes(rand); 42 result = 43 ((rand[0] & 0xff) << 24) 44 | ((rand[1] & 0xff) << 16) 45 | ((rand[2] & 0xff) << 8) 46 | (rand[3] & 0xff); 47 } 48 return result; 49 } 50 toByteFromPrintableAscii(char c)51 private static final byte toByteFromPrintableAscii(char c) { 52 if (c < '!' || c > '~') { 53 throw new TinkBugException("Not a printable ASCII character: " + c); 54 } 55 return (byte) c; 56 } 57 checkedToByteFromPrintableAscii(char c)58 private static final byte checkedToByteFromPrintableAscii(char c) 59 throws GeneralSecurityException { 60 if (c < '!' || c > '~') { 61 throw new GeneralSecurityException("Not a printable ASCII character: " + c); 62 } 63 return (byte) c; 64 } 65 66 /** 67 * Converts a string {@code s} to a corresponding {@link Bytes} object. 68 * 69 * <p>The string must contain only printable ASCII characters; calling it in any other way is a 70 * considered a bug in Tink. Spaces are not allowed. 71 * 72 * @throws TinkBugException if s contains a character which is not a printable ASCII character or 73 * a space. 74 */ toBytesFromPrintableAscii(String s)75 public static final Bytes toBytesFromPrintableAscii(String s) { 76 byte[] result = new byte[s.length()]; 77 for (int i = 0; i < s.length(); ++i) { 78 result[i] = toByteFromPrintableAscii(s.charAt(i)); 79 } 80 return Bytes.copyFrom(result); 81 } 82 83 /** 84 * Converts a string {@code s} to a corresponding {@link Bytes} object. 85 * 86 * @throws GeneralSecurityException if s contains a character which is not a printable ASCII 87 * character or a space. 88 */ checkedToBytesFromPrintableAscii(String s)89 public static final Bytes checkedToBytesFromPrintableAscii(String s) 90 throws GeneralSecurityException { 91 byte[] result = new byte[s.length()]; 92 for (int i = 0; i < s.length(); ++i) { 93 result[i] = checkedToByteFromPrintableAscii(s.charAt(i)); 94 } 95 return Bytes.copyFrom(result); 96 } 97 98 /** 99 * Best-effort checks that this is Android. 100 * 101 * <p>Note: this is more tricky than it seems. For example, using reflection to see if 102 * android.os.Build.SDK_INT exists might fail because proguard might break the 103 * reflection part. Using build dispatching can also fail if there are issues in the build graph 104 * (see cl/510374081). 105 * 106 * @return true if running on Android. 107 */ isAndroid()108 public static boolean isAndroid() { 109 // https://developer.android.com/reference/java/lang/System#getProperties%28%29 110 return Objects.equals(System.getProperty("java.vendor"), "The Android Project"); 111 } 112 113 /** Returns the current Android API level as integer or null if we do not run on Android. */ 114 @Nullable getAndroidApiLevel()115 public static Integer getAndroidApiLevel() { 116 if (!isAndroid()) { 117 return null; 118 } 119 return BuildDispatchedCode.getApiLevel(); 120 } 121 122 /** Returns true if the first argument is a prefix of the second argument. Not constant time. */ isPrefix(byte[] prefix, byte[] complete)123 public static boolean isPrefix(byte[] prefix, byte[] complete) { 124 if (complete.length < prefix.length) { 125 return false; 126 } 127 for (int i = 0; i < prefix.length; ++i) { 128 if (complete[i] != prefix[i]) { 129 return false; 130 } 131 } 132 return true; 133 } 134 135 /** 136 * Reads {@code length} number of bytes from the {@code input} stream and returns it in a {@code 137 * SecretBytes} object. 138 * 139 * <p>Note that this method will not close the {@code input} stream. 140 * 141 * @throws GeneralSecurityException when not enough randomness was provided in the {@code input} 142 * stream. 143 */ 144 @SuppressWarnings("UnusedException") readIntoSecretBytes( InputStream input, int length, SecretKeyAccess access)145 public static SecretBytes readIntoSecretBytes( 146 InputStream input, int length, SecretKeyAccess access) throws GeneralSecurityException { 147 byte[] output = new byte[length]; 148 try { 149 int len = output.length; 150 int read; 151 int readTotal = 0; 152 while (readTotal < len) { 153 read = input.read(output, readTotal, len - readTotal); 154 if (read == -1) { 155 throw new GeneralSecurityException("Not enough pseudorandomness provided"); 156 } 157 readTotal += read; 158 } 159 } catch (IOException e) { 160 throw new GeneralSecurityException("Reading pseudorandomness failed"); 161 } 162 return SecretBytes.copyFrom(output, access); 163 } 164 Util()165 private Util() {} 166 } 167