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 package com.google.crypto.tink.internal; 17 18 import static com.google.common.truth.Truth.assertThat; 19 import static java.nio.charset.StandardCharsets.US_ASCII; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertThrows; 22 import static org.junit.Assert.assertTrue; 23 24 import com.google.crypto.tink.InsecureSecretKeyAccess; 25 import com.google.crypto.tink.util.Bytes; 26 import com.google.crypto.tink.util.SecretBytes; 27 import java.io.InputStream; 28 import java.security.GeneralSecurityException; 29 import java.util.stream.IntStream; 30 import org.junit.Test; 31 import org.junit.runner.RunWith; 32 import org.junit.runners.JUnit4; 33 34 /** Tests for Tink internal Util class. */ 35 @RunWith(JUnit4.class) 36 public final class UtilTest { 37 38 @Test randKeyId_repeatedCallsShouldOutputDifferentValues()39 public void randKeyId_repeatedCallsShouldOutputDifferentValues() { 40 assertThat( 41 (int) IntStream.range(0, 4).map(unused -> Util.randKeyId()).boxed().distinct().count()) 42 .isAtLeast(2); 43 } 44 45 @Test randKeyId_repeatedCallsShouldOutputANegativeValue()46 public void randKeyId_repeatedCallsShouldOutputANegativeValue() { 47 assertThat(IntStream.range(0, 100).map(unused -> Util.randKeyId()).min().getAsInt()) 48 .isAtMost(0); 49 } 50 51 @Test toBytesFromPrintableAscii_works()52 public void toBytesFromPrintableAscii_works() throws Exception { 53 String pureAsciiString = 54 "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; 55 Bytes pureAsciiBytes = Bytes.copyFrom(pureAsciiString.getBytes(US_ASCII)); 56 assertThat(Util.toBytesFromPrintableAscii(pureAsciiString)).isEqualTo(pureAsciiBytes); 57 } 58 59 @Test toBytesFromPrintableAscii_nonAscii_throws()60 public void toBytesFromPrintableAscii_nonAscii_throws() throws Exception { 61 assertThrows(TinkBugException.class, () -> Util.toBytesFromPrintableAscii("\n")); 62 assertThrows(TinkBugException.class, () -> Util.toBytesFromPrintableAscii(" ")); 63 assertThrows(TinkBugException.class, () -> Util.toBytesFromPrintableAscii("\0x7f")); 64 assertThrows(TinkBugException.class, () -> Util.toBytesFromPrintableAscii("ö")); 65 } 66 67 @Test checkedToBytesFromPrintableAscii_works()68 public void checkedToBytesFromPrintableAscii_works() throws Exception { 69 String pureAsciiString = 70 "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; 71 Bytes pureAsciiBytes = Bytes.copyFrom(pureAsciiString.getBytes("ASCII")); 72 assertThat(Util.checkedToBytesFromPrintableAscii(pureAsciiString)).isEqualTo(pureAsciiBytes); 73 } 74 75 @Test checkedToBytesFromPrintableAscii_nonAscii_throws()76 public void checkedToBytesFromPrintableAscii_nonAscii_throws() throws Exception { 77 assertThrows(GeneralSecurityException.class, () -> Util.checkedToBytesFromPrintableAscii("\n")); 78 assertThrows(GeneralSecurityException.class, () -> Util.checkedToBytesFromPrintableAscii(" ")); 79 assertThrows( 80 GeneralSecurityException.class, () -> Util.checkedToBytesFromPrintableAscii("\0x7f")); 81 assertThrows(GeneralSecurityException.class, () -> Util.checkedToBytesFromPrintableAscii("ö")); 82 } 83 84 @Test testGetAndroidApiLevel()85 public void testGetAndroidApiLevel() throws Exception { 86 try { 87 Class<?> buildVersion = Class.forName("android.os.Build$VERSION"); 88 int expectedVersion = buildVersion.getDeclaredField("SDK_INT").getInt(null); 89 assertThat(Util.getAndroidApiLevel()).isEqualTo(expectedVersion); 90 } catch (ReflectiveOperationException e) { 91 assertThat(Util.getAndroidApiLevel()).isEqualTo(null); 92 } 93 } 94 95 @Test testIsAndroid()96 public void testIsAndroid() throws Exception { 97 try { 98 Class<?> buildVersion = Class.forName("android.os.Build$VERSION"); 99 assertThat(Util.isAndroid()).isTrue(); 100 } catch (ReflectiveOperationException e) { 101 assertThat(Util.isAndroid()).isFalse(); 102 } 103 } 104 105 @Test testIsPrefix()106 public void testIsPrefix() throws Exception { 107 assertTrue(Util.isPrefix(new byte[] {1, 2, 3}, new byte[] {1, 2, 3, 4, 5})); 108 assertTrue(Util.isPrefix(new byte[] {}, new byte[] {1, 2, 3, 4, 5})); 109 assertTrue(Util.isPrefix(new byte[] {}, new byte[] {})); 110 assertTrue(Util.isPrefix(new byte[] {5, 7, 9}, new byte[] {5, 7, 9})); 111 112 assertFalse(Util.isPrefix(new byte[] {5, 7, 9, 10}, new byte[] {5, 7, 9})); 113 assertFalse(Util.isPrefix(new byte[] {5, 7, 10}, new byte[] {5, 7, 9})); 114 assertFalse(Util.isPrefix(new byte[] {1}, new byte[] {})); 115 } 116 117 @Test readIntoSecretBytes_works()118 public void readIntoSecretBytes_works() throws Exception { 119 InputStream fragmentedInputStream = 120 new InputStream() { 121 byte nextNumber = 4; 122 123 @Override 124 public int read() { 125 return 0; 126 } 127 128 @Override 129 public int read(byte[] b, int off, int len) { 130 b[off] = nextNumber; 131 nextNumber++; 132 return 1; 133 } 134 }; 135 SecretBytes result = 136 Util.readIntoSecretBytes(fragmentedInputStream, 4, InsecureSecretKeyAccess.get()); 137 138 assertThat(result.toByteArray(InsecureSecretKeyAccess.get())) 139 .isEqualTo(new byte[] {4, 5, 6, 7}); 140 141 result = Util.readIntoSecretBytes(fragmentedInputStream, 4, InsecureSecretKeyAccess.get()); 142 assertThat(result.toByteArray(InsecureSecretKeyAccess.get())) 143 .isEqualTo(new byte[] {8, 9, 10, 11}); 144 } 145 146 @Test readIntoSecretBytes_throwsOnNotEnoughPseudorandomness()147 public void readIntoSecretBytes_throwsOnNotEnoughPseudorandomness() throws Exception { 148 byte randomness = 4; 149 InputStream shortInputStream = 150 new InputStream() { 151 int numReads = 3; 152 153 @Override 154 public int read() { 155 return 0; 156 } 157 158 @Override 159 public int read(byte[] b, int off, int len) { 160 if (numReads == 0) { 161 return -1; 162 } 163 --numReads; 164 b[off] = randomness; 165 return 1; 166 } 167 }; 168 assertThrows( 169 GeneralSecurityException.class, 170 () -> Util.readIntoSecretBytes(shortInputStream, 4, InsecureSecretKeyAccess.get())); 171 } 172 } 173