• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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