WebPushHybridDecrypt implements HybridDecrypt { @Override public byte[] decrypt(final byte[] ciphertext, final byte[] contextInfo ) throws GeneralSecurityException { if (contextInfo != null) { throw new GeneralSecurityException("contextInfo must be null because it is unused"); } if (ciphertext.length < WebPushConstants.CIPHERTEXT_OVERHEAD) { throw new GeneralSecurityException("ciphertext too short"); } if (ciphertext.length > WebPushConstants.MAX_CIPHERTEXT_SIZE) { throw new GeneralSecurityException("ciphertext too long"); } ByteBuffer record = ByteBuffer.wrap(ciphertext); byte[] salt = new byte[WebPushConstants.SALT_SIZE]; record.get(salt); int recordSize = record.getInt(); if (recordSize != this.recordSize || recordSize < ciphertext.length || recordSize > WebPushConstants.MAX_CIPHERTEXT_SIZE) { throw new GeneralSecurityException("invalid record size: " + recordSize); } int publicKeySize = (int) record.get(); if (publicKeySize != WebPushConstants.PUBLIC_KEY_SIZE) { throw new GeneralSecurityException("invalid ephemeral public key size: " + publicKeySize); } byte[] asPublicKey = new byte[WebPushConstants.PUBLIC_KEY_SIZE]; record.get(asPublicKey); ECPoint asPublicPoint = EllipticCurves.pointDecode( WebPushConstants.NIST_P256_CURVE_TYPE, WebPushConstants.UNCOMPRESSED_POINT_FORMAT, asPublicKey); byte[] payload = new byte[ciphertext.length - WebPushConstants.CONTENT_CODING_HEADER_SIZE]; record.get(payload); byte[] ecdhSecret = EllipticCurves.computeSharedSecret(recipientPrivateKey, asPublicPoint); byte[] ikm = WebPushUtil.computeIkm(ecdhSecret, authSecret, recipientPublicKey, asPublicKey); byte[] cek = WebPushUtil.computeCek(ikm, salt); byte[] nonce = WebPushUtil.computeNonce(ikm, salt); return decrypt(cek, nonce, payload); } private  WebPushHybridDecrypt(Builder builder); @Override byte[] decrypt(final byte[] ciphertext, final byte[] contextInfo /* unused */);  }
@Test public void testWithRfc8291TestVector() throws Exception { byte[] plaintext = Base64.urlSafeDecode(PLAINTEXT); byte[] recipientPrivateKey = Base64.urlSafeDecode(RECEIVER_PRIVATE_KEY); byte[] recipientPublicKey = Base64.urlSafeDecode(RECEIVER_PUBLIC_KEY); byte[] authSecret = Base64.urlSafeDecode(AUTH_SECRET); byte[] ciphertext = Base64.urlSafeDecode(CIPHERTEXT); HybridDecrypt hybridDecrypt = new WebPushHybridDecrypt.Builder() .withRecordSize(RECORD_SIZE) .withAuthSecret(authSecret) .withRecipientPublicKey(recipientPublicKey) .withRecipientPrivateKey(recipientPrivateKey) .build(); assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null )); }
@Test public void testEncryptDecryptWithInvalidRecordSizes() throws Exception { KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE); ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate(); ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic(); byte[] authSecret = Random.randBytes(16); { try { new WebPushHybridDecrypt.Builder() .withRecordSize(WebPushConstants.MAX_CIPHERTEXT_SIZE + 1) .withAuthSecret(authSecret) .withRecipientPublicKey(uaPublicKey) .withRecipientPrivateKey(uaPrivateKey) .build(); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException ex) { } try { new WebPushHybridDecrypt.Builder() .withRecordSize(WebPushConstants.CIPHERTEXT_OVERHEAD - 1) .withAuthSecret(authSecret) .withRecipientPublicKey(uaPublicKey) .withRecipientPrivateKey(uaPrivateKey) .build(); } catch (IllegalArgumentException ex) { } } { for (int i = 0; i < 50; i++) { int recordSize = WebPushConstants.CIPHERTEXT_OVERHEAD + Random.randInt( WebPushConstants.MAX_CIPHERTEXT_SIZE - WebPushConstants.CIPHERTEXT_OVERHEAD - 1); HybridEncrypt hybridEncrypt = new WebPushHybridEncrypt.Builder() .withRecordSize(recordSize) .withAuthSecret(authSecret) .withRecipientPublicKey(uaPublicKey) .build(); HybridDecrypt hybridDecrypt = new WebPushHybridDecrypt.Builder() .withRecordSize(recordSize + 1) .withAuthSecret(authSecret) .withRecipientPublicKey(uaPublicKey) .withRecipientPrivateKey(uaPrivateKey) .build(); byte[] plaintext = Random.randBytes(recordSize - WebPushConstants.CIPHERTEXT_OVERHEAD); byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null ); try { hybridDecrypt.decrypt(ciphertext, null ); fail("Expected GeneralSecurityException"); } catch (GeneralSecurityException ex) { } } } }
@Test public void testNonNullContextInfo() throws Exception { KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE); ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate(); ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic(); byte[] authSecret = Random.randBytes(16); HybridEncrypt hybridEncrypt = new WebPushHybridEncrypt.Builder() .withAuthSecret(authSecret) .withRecipientPublicKey(uaPublicKey) .build(); HybridDecrypt hybridDecrypt = new WebPushHybridDecrypt.Builder() .withAuthSecret(authSecret) .withRecipientPublicKey(uaPublicKey) .withRecipientPrivateKey(uaPrivateKey) .build(); byte[] plaintext = Random.randBytes(20); byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null ); try { byte[] contextInfo = new byte[0]; hybridDecrypt.decrypt(ciphertext, contextInfo); fail("Expected GeneralSecurityException"); } catch (GeneralSecurityException ex) { } }
@Test public void testModifyCiphertext() throws Exception { KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE); ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate(); ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic(); byte[] authSecret = Random.randBytes(16); HybridEncrypt hybridEncrypt = new WebPushHybridEncrypt.Builder() .withAuthSecret(authSecret) .withRecipientPublicKey(uaPublicKey) .build(); HybridDecrypt hybridDecrypt = new WebPushHybridDecrypt.Builder() .withAuthSecret(authSecret) .withRecipientPublicKey(uaPublicKey) .withRecipientPrivateKey(uaPrivateKey) .build(); byte[] plaintext = Random.randBytes(20); byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null ); for (int b = 0; b < ciphertext.length; b++) { for (int bit = 0; bit < 8; bit++) { byte[] modified = Arrays.copyOf(ciphertext, ciphertext.length); modified[b] ^= (byte) (1 << bit); try { byte[] unused = hybridDecrypt.decrypt(modified, null ); fail("Decrypting modified ciphertext should fail"); } catch (GeneralSecurityException ex) { } } } for (int length = 0; length < ciphertext.length; length++) { byte[] modified = Arrays.copyOf(ciphertext, length); try { byte[] unused = hybridDecrypt.decrypt(modified, null ); fail("Decrypting modified ciphertext should fail"); } catch (GeneralSecurityException ex) { } } }