// Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// package com.google.crypto.tink.internal; import com.google.crypto.tink.SecretKeyAccess; import com.google.crypto.tink.util.Bytes; import com.google.crypto.tink.util.SecretBytes; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.security.GeneralSecurityException; import java.security.SecureRandom; import java.util.Objects; import javax.annotation.Nullable; /** Helper functions used throughout Tink, for Tink internal use only. */ public final class Util { /** Android 18-compatible alternative to {@link java.nio.charset.StandardCharsets#UTF_8}. */ public static final Charset UTF_8 = Charset.forName("UTF-8"); /** Returns a positive random int which can be used as a key ID in a keyset. */ public static int randKeyId() { SecureRandom secureRandom = new SecureRandom(); byte[] rand = new byte[4]; int result = 0; while (result == 0) { secureRandom.nextBytes(rand); result = ((rand[0] & 0xff) << 24) | ((rand[1] & 0xff) << 16) | ((rand[2] & 0xff) << 8) | (rand[3] & 0xff); } return result; } private static final byte toByteFromPrintableAscii(char c) { if (c < '!' || c > '~') { throw new TinkBugException("Not a printable ASCII character: " + c); } return (byte) c; } private static final byte checkedToByteFromPrintableAscii(char c) throws GeneralSecurityException { if (c < '!' || c > '~') { throw new GeneralSecurityException("Not a printable ASCII character: " + c); } return (byte) c; } /** * Converts a string {@code s} to a corresponding {@link Bytes} object. * *

The string must contain only printable ASCII characters; calling it in any other way is a * considered a bug in Tink. Spaces are not allowed. * * @throws TinkBugException if s contains a character which is not a printable ASCII character or * a space. */ public static final Bytes toBytesFromPrintableAscii(String s) { byte[] result = new byte[s.length()]; for (int i = 0; i < s.length(); ++i) { result[i] = toByteFromPrintableAscii(s.charAt(i)); } return Bytes.copyFrom(result); } /** * Converts a string {@code s} to a corresponding {@link Bytes} object. * * @throws GeneralSecurityException if s contains a character which is not a printable ASCII * character or a space. */ public static final Bytes checkedToBytesFromPrintableAscii(String s) throws GeneralSecurityException { byte[] result = new byte[s.length()]; for (int i = 0; i < s.length(); ++i) { result[i] = checkedToByteFromPrintableAscii(s.charAt(i)); } return Bytes.copyFrom(result); } /** * Best-effort checks that this is Android. * *

Note: this is more tricky than it seems. For example, using reflection to see if * android.os.Build.SDK_INT exists might fail because proguard might break the * reflection part. Using build dispatching can also fail if there are issues in the build graph * (see cl/510374081). * * @return true if running on Android. */ public static boolean isAndroid() { // https://developer.android.com/reference/java/lang/System#getProperties%28%29 return Objects.equals(System.getProperty("java.vendor"), "The Android Project"); } /** Returns the current Android API level as integer or null if we do not run on Android. */ @Nullable public static Integer getAndroidApiLevel() { if (!isAndroid()) { return null; } return BuildDispatchedCode.getApiLevel(); } /** Returns true if the first argument is a prefix of the second argument. Not constant time. */ public static boolean isPrefix(byte[] prefix, byte[] complete) { if (complete.length < prefix.length) { return false; } for (int i = 0; i < prefix.length; ++i) { if (complete[i] != prefix[i]) { return false; } } return true; } /** * Reads {@code length} number of bytes from the {@code input} stream and returns it in a {@code * SecretBytes} object. * *

Note that this method will not close the {@code input} stream. * * @throws GeneralSecurityException when not enough randomness was provided in the {@code input} * stream. */ @SuppressWarnings("UnusedException") public static SecretBytes readIntoSecretBytes( InputStream input, int length, SecretKeyAccess access) throws GeneralSecurityException { byte[] output = new byte[length]; try { int len = output.length; int read; int readTotal = 0; while (readTotal < len) { read = input.read(output, readTotal, len - readTotal); if (read == -1) { throw new GeneralSecurityException("Not enough pseudorandomness provided"); } readTotal += read; } } catch (IOException e) { throw new GeneralSecurityException("Reading pseudorandomness failed"); } return SecretBytes.copyFrom(output, access); } private Util() {} }