diff options
Diffstat (limited to 'src/main/java/com/google/security/cryptauth/lib/securemessage/SecureMessageBuilder.java')
-rw-r--r-- | src/main/java/com/google/security/cryptauth/lib/securemessage/SecureMessageBuilder.java | 277 |
1 files changed, 0 insertions, 277 deletions
diff --git a/src/main/java/com/google/security/cryptauth/lib/securemessage/SecureMessageBuilder.java b/src/main/java/com/google/security/cryptauth/lib/securemessage/SecureMessageBuilder.java deleted file mode 100644 index f1a9464..0000000 --- a/src/main/java/com/google/security/cryptauth/lib/securemessage/SecureMessageBuilder.java +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright 2020 Google LLC -// -// 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 -// -// https://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.security.cryptauth.lib.securemessage; - -import com.google.protobuf.ByteString; -import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType; -import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType; -import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.Header; -import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBodyInternal; -import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.Arrays; -import javax.annotation.Nullable; - -/** - * Builder for {@link SecureMessage} protos. Can be used to create either signed messages, - * or "signcrypted" (encrypted then signed) messages that include a tight binding between the - * ciphertext portion and a verification key identity. - * - * @see SecureMessageParser - */ -public class SecureMessageBuilder { - private ByteString publicMetadata; - private ByteString verificationKeyId; - private ByteString decryptionKeyId; - /** - * This data is never sent inside the protobufs, so the builder just saves it as a byte[]. - */ - private byte[] associatedData; - - private SecureRandom rng; - - public SecureMessageBuilder() { - reset(); - this.rng = new SecureRandom(); - } - - /** - * Resets this {@link SecureMessageBuilder} instance to a blank configuration (and returns it). - */ - public SecureMessageBuilder reset() { - this.publicMetadata = null; - this.verificationKeyId = null; - this.decryptionKeyId = null; - this.associatedData = null; - return this; - } - - /** - * Optional metadata to be sent along with the header information in this {@link SecureMessage}. - * <p> - * Note that this value will be sent <em>UNENCRYPTED</em> in all cases. - * <p> - * Can be used with either cleartext or signcrypted messages, but is intended primarily for use - * with signcrypted messages. - */ - public SecureMessageBuilder setPublicMetadata(byte[] publicMetadata) { - this.publicMetadata = ByteString.copyFrom(publicMetadata); - return this; - } - - /** - * The recipient of the {@link SecureMessage} should be able to uniquely determine the correct - * verification key, given only this value. - * <p> - * Can be used with either cleartext or signcrypted messages. Setting this is mandatory for - * signcrypted messages using a public key {@link SigType}, in order to bind the encrypted - * body to a specific verification key. - * <p> - * Note that this value is sent <em>UNENCRYPTED</em> in all cases. - */ - public SecureMessageBuilder setVerificationKeyId(byte[] verificationKeyId) { - this.verificationKeyId = ByteString.copyFrom(verificationKeyId); - return this; - } - - /** - * To be used only with {@link #buildSignCryptedMessage(Key, SigType, Key, EncType, byte[])}, - * this value is sent <em>UNENCRYPTED</em> as part of the header. It should be used by the - * recipient of the {@link SecureMessage} to identify an appropriate key to use for decrypting - * the message body. - */ - public SecureMessageBuilder setDecryptionKeyId(byte[] decryptionKeyId) { - this.decryptionKeyId = ByteString.copyFrom(decryptionKeyId); - return this; - } - - /** - * Additional data is "associated" with this {@link SecureMessage}, but will not be sent as - * part of it. The recipient of the {@link SecureMessage} will need to provide the same data in - * order to verify the message body. Setting this to {@code null} is equivalent to using an - * empty array (unlike the behavior of {@code VerificationKeyId} and {@code DecryptionKeyId}). - * <p> - * Note that the <em>size</em> (length in bytes) of the associated data will be sent in the - * <em>UNENCRYPTED</em> header information, even if you are using encryption. - * <p> - * If you will be using {@link #buildSignedCleartextMessage(Key, SigType, byte[])}, then anyone - * observing the {@link SecureMessage} may be able to infer this associated data via an - * "offline dictionary attack". That is, when no encryption is used, you will not be hiding this - * data simply because it is not being sent over the wire. - */ - public SecureMessageBuilder setAssociatedData(@Nullable byte[] associatedData) { - this.associatedData = associatedData; - return this; - } - - // @VisibleForTesting - SecureMessageBuilder setRng(SecureRandom rng) { - this.rng = rng; - return this; - } - - /** - * Generates a signed {@link SecureMessage} with the payload {@code body} left - * <em>UNENCRYPTED</em>. - * - * <p>Note that if you have used {@link #setAssociatedData(byte[])}, the associated data will - * be subject to offline dictionary attacks if you use a public key {@link SigType}. - * - * <p>Doesn't currently support symmetric keys stored in a TPM (since we access the raw key). - * - * @see SecureMessageParser#parseSignedCleartextMessage(SecureMessage, Key, SigType) - */ - public SecureMessage buildSignedCleartextMessage(Key signingKey, SigType sigType, byte[] body) - throws NoSuchAlgorithmException, InvalidKeyException { - if ((signingKey == null) || (sigType == null) || (body == null)) { - throw new NullPointerException(); - } - if (decryptionKeyId != null) { - throw new IllegalStateException("Cannot set decryptionKeyId for a cleartext message"); - } - - byte[] headerAndBody = serializeHeaderAndBody( - buildHeader(sigType, EncType.NONE, null).toByteArray(), body); - return createSignedResult(signingKey, sigType, headerAndBody, associatedData); - } - - /** - * Generates a signed and encrypted {@link SecureMessage}. If the signature type requires a public - * key, such as with ECDSA_P256_SHA256, then the caller <em>must</em> set a verification id using - * the {@link #setVerificationKeyId(byte[])} method. The verification key id will be bound to the - * encrypted {@code body}, preventing attacks that involve stripping the signature and then - * re-signing the encrypted {@code body} as if it was originally sent by the attacker. - * - * <p> - * It is safe to re-use one {@link javax.crypto.SecretKey} as both {@code signingKey} and - * {@code encryptionKey}, even if that key is also used for - * {@link #buildSignedCleartextMessage(Key, SigType, byte[])}. In fact, the resulting output - * encoding will be more compact when the same symmetric key is used for both. - * - * <p> - * Note that PublicMetadata and other header fields are left <em>UNENCRYPTED</em>. - * - * <p> - * Doesn't currently support symmetric keys stored in a TPM (since we access the raw key). - * - * @param encType <em>must not</em> be set to {@link EncType#NONE} - * @see SecureMessageParser#parseSignCryptedMessage(SecureMessage, Key, SigType, Key, EncType) - */ - public SecureMessage buildSignCryptedMessage( - Key signingKey, SigType sigType, Key encryptionKey, EncType encType, byte[] body) - throws NoSuchAlgorithmException, InvalidKeyException { - if ((signingKey == null) - || (sigType == null) - || (encryptionKey == null) - || (encType == null) - || (body == null)) { - throw new NullPointerException(); - } - if (encType == EncType.NONE) { - throw new IllegalArgumentException(encType + " not supported for encrypted messages"); - } - if (sigType.isPublicKeyScheme() && (verificationKeyId == null)) { - throw new IllegalStateException( - "Must set a verificationKeyId when using public key signature with encryption"); - } - - byte[] iv = CryptoOps.generateIv(encType, rng); - byte[] header = buildHeader(sigType, encType, iv).toByteArray(); - - // We may or may not need an extra tag in front of the plaintext body - byte[] taggedBody; - // We will only sign the associated data when we don't tag the plaintext body - byte[] associatedDataToBeSigned; - if (taggedPlaintextRequired(signingKey, sigType, encryptionKey)) { - // Place a "tag" in front of the the plaintext message containing a digest of the header - taggedBody = CryptoOps.concat( - // Digest the header + any associated data, yielding a tag to be encrypted with the body. - CryptoOps.digest(CryptoOps.concat(header, associatedData)), - body); - associatedDataToBeSigned = null; // We already handled any associatedData via the tag - } else { - taggedBody = body; - associatedDataToBeSigned = associatedData; - } - - // Compute the encrypted body, which binds the tag to the message inside the ciphertext - byte[] encryptedBody = CryptoOps.encrypt(encryptionKey, encType, rng, iv, taggedBody); - - byte[] headerAndBody = serializeHeaderAndBody(header, encryptedBody); - return createSignedResult(signingKey, sigType, headerAndBody, associatedDataToBeSigned); - } - - /** - * Indicates whether a "tag" is needed next to the plaintext body inside the ciphertext, to - * prevent the same ciphertext from being reused with someone else's signature on it. - */ - static boolean taggedPlaintextRequired(Key signingKey, SigType sigType, Key encryptionKey) { - // We need a tag if different keys are being used to "sign" vs. encrypt - return sigType.isPublicKeyScheme() - || !Arrays.equals(signingKey.getEncoded(), encryptionKey.getEncoded()); - } - - /** - * @param iv IV or {@code null} if IV to be left unset in the Header - */ - private Header buildHeader(SigType sigType, EncType encType, byte[] iv) { - Header.Builder result = Header.newBuilder() - .setSignatureScheme(sigType.getSigScheme()) - .setEncryptionScheme(encType.getEncScheme()); - if (verificationKeyId != null) { - result.setVerificationKeyId(verificationKeyId); - } - if (decryptionKeyId != null) { - result.setDecryptionKeyId(decryptionKeyId); - } - if (publicMetadata != null) { - result.setPublicMetadata(publicMetadata); - } - if (associatedData != null) { - result.setAssociatedDataLength(associatedData.length); - } - if (iv != null) { - result.setIv(ByteString.copyFrom(iv)); - } - return result.build(); - } - - /** - * @param header a serialized representation of a {@link Header} - * @param body arbitrary payload data - * @return a serialized representation of a {@link SecureMessageProto.HeaderAndBody} - */ - private byte[] serializeHeaderAndBody(byte[] header, byte[] body) { - return HeaderAndBodyInternal.newBuilder() - .setHeader(ByteString.copyFrom(header)) - .setBody(ByteString.copyFrom(body)) - .build() - .toByteArray(); - } - - private SecureMessage createSignedResult( - Key signingKey, SigType sigType, byte[] headerAndBody, @Nullable byte[] associatedData) - throws NoSuchAlgorithmException, InvalidKeyException { - byte[] sig = - CryptoOps.sign(sigType, signingKey, rng, CryptoOps.concat(headerAndBody, associatedData)); - return SecureMessage.newBuilder() - .setHeaderAndBody(ByteString.copyFrom(headerAndBody)) - .setSignature(ByteString.copyFrom(sig)) - .build(); - } -} |