blob: 34d0918551dc9e93ecb16cf27dbd217984f29c25 [file] [log] [blame]
// Copyright 2017 Google Inc.
//
// 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.subtle;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.WritableByteChannel;
import java.security.GeneralSecurityException;
/**
* An instance of {@link WritableByteChannel} that encrypts the input using a nonce based online
* authentication scheme.
*/
class StreamingAeadEncryptingChannel implements WritableByteChannel {
private WritableByteChannel ciphertextChannel;
private StreamSegmentEncrypter encrypter;
ByteBuffer ptBuffer; // contains plaintext that has not yet been encrypted.
ByteBuffer ctBuffer; // contains ciphertext that has not been written to ciphertextChannel.
private int plaintextSegmentSize;
boolean open = true;
public StreamingAeadEncryptingChannel(
NonceBasedStreamingAead streamAead,
WritableByteChannel ciphertextChannel,
byte[] associatedData) throws GeneralSecurityException, IOException {
this.ciphertextChannel = ciphertextChannel;
encrypter = streamAead.newStreamSegmentEncrypter(associatedData);
plaintextSegmentSize = streamAead.getPlaintextSegmentSize();
ptBuffer = ByteBuffer.allocate(plaintextSegmentSize);
ptBuffer.limit(plaintextSegmentSize - streamAead.getCiphertextOffset());
ctBuffer = ByteBuffer.allocate(streamAead.getCiphertextSegmentSize());
// At this point, ciphertextChannel might not yet be ready to receive bytes.
// Buffering the header in ctBuffer ensures that the header will be written when writing to
// ciphertextChannel is possible.
ctBuffer.put(encrypter.getHeader());
ctBuffer.flip();
ciphertextChannel.write(ctBuffer);
}
@Override
public synchronized int write(ByteBuffer pt) throws IOException {
if (!open) {
throw new ClosedChannelException();
}
if (ctBuffer.remaining() > 0) {
ciphertextChannel.write(ctBuffer);
}
int startPosition = pt.position();
while (pt.remaining() > ptBuffer.remaining()) {
if (ctBuffer.remaining() > 0) {
return pt.position() - startPosition;
}
int sliceSize = ptBuffer.remaining();
ByteBuffer slice = pt.slice();
slice.limit(sliceSize);
pt.position(pt.position() + sliceSize);
try {
ptBuffer.flip();
ctBuffer.clear();
if (slice.remaining() != 0) {
encrypter.encryptSegment(ptBuffer, slice, false, ctBuffer);
} else {
encrypter.encryptSegment(ptBuffer, false, ctBuffer);
}
} catch (GeneralSecurityException ex) {
throw new IOException(ex);
}
ctBuffer.flip();
ciphertextChannel.write(ctBuffer);
ptBuffer.clear();
ptBuffer.limit(plaintextSegmentSize);
}
ptBuffer.put(pt);
return pt.position() - startPosition;
}
@Override
public synchronized void close() throws IOException {
if (!open) {
return;
}
// TODO(bleichen): Is there a way to fully write the remaining ciphertext?
// The following is the strategy from java.nio.channels.Channels.writeFullyImpl
// I.e. try writing as long as at least one byte is written.
while (ctBuffer.remaining() > 0) {
int n = ciphertextChannel.write(ctBuffer);
if (n <= 0) {
throw new IOException("Failed to write ciphertext before closing");
}
}
try {
ctBuffer.clear();
ptBuffer.flip();
encrypter.encryptSegment(ptBuffer, true, ctBuffer);
} catch (GeneralSecurityException ex) {
// TODO(bleichen): define the state of this. E.g. open = false;
throw new IOException(ex);
}
ctBuffer.flip();
while (ctBuffer.remaining() > 0) {
int n = ciphertextChannel.write(ctBuffer);
if (n <= 0) {
throw new IOException("Failed to write ciphertext before closing");
}
}
ciphertextChannel.close();
open = false;
}
@Override
public boolean isOpen() {
return open;
}
}