1 /* Copyright 2018 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 * https://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 package com.google.security.cryptauth.lib.securemessage; 16 17 import com.google.protobuf.ByteString; 18 import com.google.protobuf.InvalidProtocolBufferException; 19 import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType; 20 import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType; 21 import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.Header; 22 import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBody; 23 import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBodyInternal; 24 import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage; 25 import java.security.InvalidAlgorithmParameterException; 26 import java.security.InvalidKeyException; 27 import java.security.Key; 28 import java.security.NoSuchAlgorithmException; 29 import java.security.SignatureException; 30 import javax.annotation.Nullable; 31 import javax.crypto.BadPaddingException; 32 import javax.crypto.IllegalBlockSizeException; 33 34 /** 35 * Utility class to parse and verify {@link SecureMessage} protos. Verifies the signature on the 36 * message, and decrypts "signcrypted" messages (while simultaneously verifying the signature). 37 * 38 * @see SecureMessageBuilder 39 */ 40 public class SecureMessageParser { 41 SecureMessageParser()42 private SecureMessageParser() {} // Do not instantiate 43 44 /** 45 * Extracts the {@link Header} component from a {@link SecureMessage} but <em>DOES NOT VERIFY</em> 46 * the signature when doing so. Callers should not trust the resulting output until after a 47 * subsequent {@code parse*()} call has succeeded. 48 * 49 * <p>The intention is to allow the caller to determine the type of the protocol message and which 50 * keys are in use, prior to attempting to verify (and possibly decrypt) the payload body. 51 */ getUnverifiedHeader(SecureMessage secmsg)52 public static Header getUnverifiedHeader(SecureMessage secmsg) 53 throws InvalidProtocolBufferException { 54 if (!secmsg.hasHeaderAndBody()) { 55 throw new InvalidProtocolBufferException("Missing header and body"); 56 } 57 if (!HeaderAndBody.parseFrom(secmsg.getHeaderAndBody()).hasHeader()) { 58 throw new InvalidProtocolBufferException("Missing header"); 59 } 60 Header result = HeaderAndBody.parseFrom(secmsg.getHeaderAndBody()).getHeader(); 61 // Check that at least a signature scheme was set 62 if (!result.hasSignatureScheme()) { 63 throw new InvalidProtocolBufferException("Missing header field(s)"); 64 } 65 // Check signature scheme is legal 66 try { 67 SigType.valueOf(result.getSignatureScheme()); 68 } catch (IllegalArgumentException e) { 69 throw new InvalidProtocolBufferException("Corrupt/unsupported SignatureScheme"); 70 } 71 // Check encryption scheme is legal 72 if (result.hasEncryptionScheme()) { 73 try { 74 EncType.valueOf(result.getEncryptionScheme()); 75 } catch (IllegalArgumentException e) { 76 throw new InvalidProtocolBufferException("Corrupt/unsupported EncryptionScheme"); 77 } 78 } 79 return result; 80 } 81 82 /** 83 * Parses a {@link SecureMessage} containing a cleartext payload body, and verifies the signature. 84 * 85 * @return the parsed {@link HeaderAndBody} pair (which is fully verified) 86 * @throws SignatureException if signature verification fails 87 * @see SecureMessageBuilder#buildSignedCleartextMessage(Key, SigType, byte[]) 88 */ parseSignedCleartextMessage( SecureMessage secmsg, Key verificationKey, SigType sigType)89 public static HeaderAndBody parseSignedCleartextMessage( 90 SecureMessage secmsg, Key verificationKey, SigType sigType) 91 throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { 92 return parseSignedCleartextMessage(secmsg, verificationKey, sigType, null); 93 } 94 95 /** 96 * Parses a {@link SecureMessage} containing a cleartext payload body, and verifies the signature. 97 * 98 * @param associatedData optional associated data bound to the signature (but not in the message) 99 * @return the parsed {@link HeaderAndBody} pair (which is fully verified) 100 * @throws SignatureException if signature verification fails 101 * @see SecureMessageBuilder#buildSignedCleartextMessage(Key, SigType, byte[]) 102 */ parseSignedCleartextMessage( SecureMessage secmsg, Key verificationKey, SigType sigType, @Nullable byte[] associatedData)103 public static HeaderAndBody parseSignedCleartextMessage( 104 SecureMessage secmsg, Key verificationKey, SigType sigType, @Nullable byte[] associatedData) 105 throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { 106 if ((secmsg == null) || (verificationKey == null) || (sigType == null)) { 107 throw new NullPointerException(); 108 } 109 return verifyHeaderAndBody( 110 secmsg, 111 verificationKey, 112 sigType, 113 EncType.NONE, 114 associatedData, 115 false /* suppressAssociatedData is always false for signed cleartext */); 116 } 117 118 /** 119 * Parses a {@link SecureMessage} containing an encrypted payload body, extracting a decryption of 120 * the payload body and verifying the signature. 121 * 122 * @return the parsed {@link HeaderAndBody} pair (which is fully verified and decrypted) 123 * @throws SignatureException if signature verification fails 124 * @see SecureMessageBuilder#buildSignCryptedMessage(Key, SigType, Key, EncType, byte[]) 125 */ parseSignCryptedMessage( SecureMessage secmsg, Key verificationKey, SigType sigType, Key decryptionKey, EncType encType)126 public static HeaderAndBody parseSignCryptedMessage( 127 SecureMessage secmsg, 128 Key verificationKey, 129 SigType sigType, 130 Key decryptionKey, 131 EncType encType) 132 throws InvalidKeyException, NoSuchAlgorithmException, SignatureException { 133 return parseSignCryptedMessage(secmsg, verificationKey, sigType, decryptionKey, encType, null); 134 } 135 136 /** 137 * Parses a {@link SecureMessage} containing an encrypted payload body, extracting a decryption of 138 * the payload body and verifying the signature. 139 * 140 * @param associatedData optional associated data bound to the signature (but not in the message) 141 * @return the parsed {@link HeaderAndBody} pair (which is fully verified and decrypted) 142 * @throws SignatureException if signature verification fails 143 * @see SecureMessageBuilder#buildSignCryptedMessage(Key, SigType, Key, EncType, byte[]) 144 */ parseSignCryptedMessage( SecureMessage secmsg, Key verificationKey, SigType sigType, Key decryptionKey, EncType encType, @Nullable byte[] associatedData)145 public static HeaderAndBody parseSignCryptedMessage( 146 SecureMessage secmsg, 147 Key verificationKey, 148 SigType sigType, 149 Key decryptionKey, 150 EncType encType, 151 @Nullable byte[] associatedData) 152 throws InvalidKeyException, NoSuchAlgorithmException, SignatureException { 153 if ((secmsg == null) 154 || (verificationKey == null) 155 || (sigType == null) 156 || (decryptionKey == null) 157 || (encType == null)) { 158 throw new NullPointerException(); 159 } 160 if (encType == EncType.NONE) { 161 throw new SignatureException("Not a signcrypted message"); 162 } 163 164 boolean tagRequired = 165 SecureMessageBuilder.taggedPlaintextRequired(verificationKey, sigType, decryptionKey); 166 HeaderAndBody headerAndEncryptedBody; 167 headerAndEncryptedBody = verifyHeaderAndBody( 168 secmsg, 169 verificationKey, 170 sigType, 171 encType, 172 associatedData, 173 tagRequired /* suppressAssociatedData if it is handled by the tag instead */); 174 175 byte[] rawDecryptedBody; 176 Header header = headerAndEncryptedBody.getHeader(); 177 if (!header.hasIv()) { 178 throw new SignatureException(); 179 } 180 try { 181 rawDecryptedBody = CryptoOps.decrypt( 182 decryptionKey, encType, header.getIv().toByteArray(), 183 headerAndEncryptedBody.getBody().toByteArray()); 184 } catch (InvalidAlgorithmParameterException e) { 185 throw new SignatureException(); 186 } catch (IllegalBlockSizeException e) { 187 throw new SignatureException(); 188 } catch (BadPaddingException e) { 189 throw new SignatureException(); 190 } 191 192 if (!tagRequired) { 193 // No tag expected, so we're all done 194 return HeaderAndBody.newBuilder(headerAndEncryptedBody) 195 .setBody(ByteString.copyFrom(rawDecryptedBody)) 196 .build(); 197 } 198 199 // Verify the tag that binds the ciphertext to the header, and remove it 200 byte[] headerBytes; 201 try { 202 headerBytes = 203 HeaderAndBodyInternal.parseFrom(secmsg.getHeaderAndBody()).getHeader().toByteArray(); 204 } catch (InvalidProtocolBufferException e) { 205 // This shouldn't happen, but throw it up just in case 206 throw new SignatureException(e); 207 } 208 boolean verifiedBinding = false; 209 byte[] expectedTag = CryptoOps.digest(CryptoOps.concat(headerBytes, associatedData)); 210 if (rawDecryptedBody.length >= CryptoOps.DIGEST_LENGTH) { 211 byte[] actualTag = CryptoOps.subarray(rawDecryptedBody, 0, CryptoOps.DIGEST_LENGTH); 212 if (CryptoOps.constantTimeArrayEquals(actualTag, expectedTag)) { 213 verifiedBinding = true; 214 } 215 } 216 if (!verifiedBinding) { 217 throw new SignatureException(); 218 } 219 220 int bodyLen = rawDecryptedBody.length - CryptoOps.DIGEST_LENGTH; 221 return HeaderAndBody.newBuilder(headerAndEncryptedBody) 222 // Remove the tag and set the plaintext body 223 .setBody(ByteString.copyFrom(rawDecryptedBody, CryptoOps.DIGEST_LENGTH, bodyLen)) 224 .build(); 225 } 226 verifyHeaderAndBody( SecureMessage secmsg, Key verificationKey, SigType sigType, EncType encType, @Nullable byte[] associatedData, boolean suppressAssociatedData )227 private static HeaderAndBody verifyHeaderAndBody( 228 SecureMessage secmsg, 229 Key verificationKey, 230 SigType sigType, 231 EncType encType, 232 @Nullable byte[] associatedData, 233 boolean suppressAssociatedData /* in case it is in the tag instead */) 234 throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { 235 if (!secmsg.hasHeaderAndBody() || !secmsg.hasSignature()) { 236 throw new SignatureException("Signature failed verification"); 237 } 238 byte[] signature = secmsg.getSignature().toByteArray(); 239 byte[] data = secmsg.getHeaderAndBody().toByteArray(); 240 byte[] signedData = suppressAssociatedData ? data : CryptoOps.concat(data, associatedData); 241 242 // Try not to leak the specific reason for verification failures, due to security concerns. 243 boolean verified = CryptoOps.verify(verificationKey, sigType, signature, signedData); 244 HeaderAndBody result = null; 245 try { 246 result = HeaderAndBody.parseFrom(secmsg.getHeaderAndBody()); 247 // Even if declared required, micro proto doesn't throw an exception if fields are not present 248 if (!result.hasHeader() || !result.hasBody()) { 249 throw new SignatureException("Signature failed verification"); 250 } 251 verified &= (result.getHeader().getSignatureScheme() == sigType.getSigScheme()); 252 verified &= (result.getHeader().getEncryptionScheme() == encType.getEncScheme()); 253 // Check that either a decryption operation is expected, or no DecryptionKeyId is set. 254 verified &= (encType != EncType.NONE) || !result.getHeader().hasDecryptionKeyId(); 255 // If encryption was used, check that either we are not using a public key signature or a 256 // VerificationKeyId was set (as is required for public key based signature + encryption). 257 verified &= (encType == EncType.NONE) || !sigType.isPublicKeyScheme() || 258 result.getHeader().hasVerificationKeyId(); 259 int associatedDataLength = associatedData == null ? 0 : associatedData.length; 260 verified &= (result.getHeader().getAssociatedDataLength() == associatedDataLength); 261 } catch (InvalidProtocolBufferException e) { 262 verified = false; 263 } 264 265 if (verified) { 266 return result; 267 } 268 throw new SignatureException("Signature failed verification"); 269 } 270 } 271