• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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