|
| 1 | +package org.bouncycastle.tls; |
| 2 | + |
| 3 | +import java.security.SecureRandom; |
| 4 | + |
| 5 | +import org.bouncycastle.crypto.engines.AESEngine; |
| 6 | +import org.bouncycastle.crypto.engines.ChaCha7539Engine; |
| 7 | +import org.bouncycastle.crypto.params.KeyParameter; |
| 8 | +import org.bouncycastle.crypto.params.ParametersWithIV; |
| 9 | +import org.bouncycastle.jcajce.util.DefaultJcaJceHelper; |
| 10 | +import org.bouncycastle.tls.crypto.impl.TlsRecordNumberMask; |
| 11 | +import org.bouncycastle.tls.crypto.impl.bc.BcTlsAESRecordNumberMask; |
| 12 | +import org.bouncycastle.tls.crypto.impl.bc.BcTlsChaCha20RecordNumberMask; |
| 13 | +import org.bouncycastle.tls.crypto.impl.jcajce.JceAESRecordNumberMask; |
| 14 | +import org.bouncycastle.tls.crypto.impl.jcajce.JceChaCha20RecordNumberMask; |
| 15 | +import org.bouncycastle.util.Arrays; |
| 16 | + |
| 17 | +import junit.framework.TestCase; |
| 18 | + |
| 19 | +/** |
| 20 | + * RFC 9147 4.2.3 record number encryption masks: AES-ECB over the first 16 ciphertext bytes, or the ChaCha20 |
| 21 | + * block selected by the first 4 ciphertext bytes (counter) and the next 12 (nonce). Both backends must agree |
| 22 | + * with an independent lightweight reference. |
| 23 | + */ |
| 24 | +public class DTLSRecordNumberMaskTest |
| 25 | + extends TestCase |
| 26 | +{ |
| 27 | + private static final SecureRandom RANDOM = new SecureRandom(); |
| 28 | + |
| 29 | + public void testAESMaskMatchesECBReference() throws Exception |
| 30 | + { |
| 31 | + byte[] key = new byte[16]; |
| 32 | + byte[] ciphertext = new byte[40]; |
| 33 | + RANDOM.nextBytes(key); |
| 34 | + RANDOM.nextBytes(ciphertext); |
| 35 | + |
| 36 | + AESEngine reference = new AESEngine(); |
| 37 | + reference.init(true, new KeyParameter(key)); |
| 38 | + byte[] expected = new byte[16]; |
| 39 | + reference.processBlock(ciphertext, 8, expected, 0); |
| 40 | + |
| 41 | + TlsRecordNumberMask bc = new BcTlsAESRecordNumberMask(new AESEngine()); |
| 42 | + bc.setKey(key, 0, key.length); |
| 43 | + byte[] actualBc = new byte[16]; |
| 44 | + bc.generateMask(ciphertext, 8, actualBc, 0); |
| 45 | + assertTrue(Arrays.areEqual(expected, actualBc)); |
| 46 | + |
| 47 | + TlsRecordNumberMask jce = new JceAESRecordNumberMask(new DefaultJcaJceHelper()); |
| 48 | + jce.setKey(key, 0, key.length); |
| 49 | + byte[] actualJce = new byte[16]; |
| 50 | + jce.generateMask(ciphertext, 8, actualJce, 0); |
| 51 | + assertTrue(Arrays.areEqual(expected, actualJce)); |
| 52 | + } |
| 53 | + |
| 54 | + public void testChaCha20MaskMatchesKeystreamReference() throws Exception |
| 55 | + { |
| 56 | + byte[] key = new byte[32]; |
| 57 | + byte[] ciphertext = new byte[32]; |
| 58 | + RANDOM.nextBytes(key); |
| 59 | + RANDOM.nextBytes(ciphertext); |
| 60 | + |
| 61 | + // counter = 3 (little-endian), so the mask is keystream bytes [192, 208) |
| 62 | + ciphertext[0] = 3; |
| 63 | + ciphertext[1] = 0; |
| 64 | + ciphertext[2] = 0; |
| 65 | + ciphertext[3] = 0; |
| 66 | + |
| 67 | + byte[] nonce = Arrays.copyOfRange(ciphertext, 4, 16); |
| 68 | + ChaCha7539Engine reference = new ChaCha7539Engine(); |
| 69 | + reference.init(true, new ParametersWithIV(new KeyParameter(key), nonce)); |
| 70 | + byte[] stream = new byte[3 * 64 + 16]; |
| 71 | + reference.processBytes(stream, 0, stream.length, stream, 0); |
| 72 | + byte[] expected = Arrays.copyOfRange(stream, 192, 208); |
| 73 | + |
| 74 | + TlsRecordNumberMask bc = new BcTlsChaCha20RecordNumberMask(); |
| 75 | + bc.setKey(key, 0, key.length); |
| 76 | + byte[] actualBc = new byte[16]; |
| 77 | + bc.generateMask(ciphertext, 0, actualBc, 0); |
| 78 | + assertTrue(Arrays.areEqual(expected, actualBc)); |
| 79 | + |
| 80 | + TlsRecordNumberMask jce = new JceChaCha20RecordNumberMask(); |
| 81 | + jce.setKey(key, 0, key.length); |
| 82 | + byte[] actualJce = new byte[16]; |
| 83 | + jce.generateMask(ciphertext, 0, actualJce, 0); |
| 84 | + assertTrue(Arrays.areEqual(expected, actualJce)); |
| 85 | + } |
| 86 | + |
| 87 | + public void testChaCha20MaskLargeCounter() throws Exception |
| 88 | + { |
| 89 | + byte[] key = new byte[32]; |
| 90 | + byte[] ciphertext = new byte[16]; |
| 91 | + RANDOM.nextBytes(key); |
| 92 | + RANDOM.nextBytes(ciphertext); |
| 93 | + ciphertext[3] = (byte)0xFF; // counter near 2^32 |
| 94 | + |
| 95 | + TlsRecordNumberMask bc = new BcTlsChaCha20RecordNumberMask(); |
| 96 | + bc.setKey(key, 0, key.length); |
| 97 | + byte[] a = new byte[16]; |
| 98 | + bc.generateMask(ciphertext, 0, a, 0); |
| 99 | + |
| 100 | + TlsRecordNumberMask jce = new JceChaCha20RecordNumberMask(); |
| 101 | + jce.setKey(key, 0, key.length); |
| 102 | + byte[] b = new byte[16]; |
| 103 | + jce.generateMask(ciphertext, 0, b, 0); |
| 104 | + |
| 105 | + assertTrue(Arrays.areEqual(a, b)); |
| 106 | + assertFalse(Arrays.areEqual(new byte[16], a)); |
| 107 | + } |
| 108 | +} |
0 commit comments