• 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.securegcm;
16 
17 import com.google.common.annotations.VisibleForTesting;
18 import com.google.protobuf.InvalidProtocolBufferException;
19 import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.DeviceToDeviceMessage;
20 import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.ResponderHello;
21 import com.google.security.cryptauth.lib.securegcm.SecureGcmProto.GcmMetadata;
22 import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.Payload;
23 import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.PayloadType;
24 import com.google.security.cryptauth.lib.securemessage.CryptoOps;
25 import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType;
26 import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType;
27 import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil;
28 import com.google.security.cryptauth.lib.securemessage.SecureMessageBuilder;
29 import com.google.security.cryptauth.lib.securemessage.SecureMessageParser;
30 import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey;
31 import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.Header;
32 import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBody;
33 import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage;
34 import java.security.InvalidKeyException;
35 import java.security.NoSuchAlgorithmException;
36 import java.security.PrivateKey;
37 import java.security.PublicKey;
38 import java.security.SignatureException;
39 import java.security.spec.InvalidKeySpecException;
40 import javax.annotation.Nullable;
41 import javax.crypto.SecretKey;
42 
43 /**
44  * A collection of static utility methods used by {@link D2DHandshakeContext} for the Device to
45  * Device communication (D2D) library.
46  */
47 class D2DCryptoOps {
48   // SHA256 of "D2D"
49   // package-private
50   static final byte[] SALT = new byte[] {
51     (byte) 0x82, (byte) 0xAA, (byte) 0x55, (byte) 0xA0, (byte) 0xD3, (byte) 0x97, (byte) 0xF8,
52     (byte) 0x83, (byte) 0x46, (byte) 0xCA, (byte) 0x1C, (byte) 0xEE, (byte) 0x8D, (byte) 0x39,
53     (byte) 0x09, (byte) 0xB9, (byte) 0x5F, (byte) 0x13, (byte) 0xFA, (byte) 0x7D, (byte) 0xEB,
54     (byte) 0x1D, (byte) 0x4A, (byte) 0xB3, (byte) 0x83, (byte) 0x76, (byte) 0xB8, (byte) 0x25,
55     (byte) 0x6D, (byte) 0xA8, (byte) 0x55, (byte) 0x10
56   };
57 
58   // Don't instantiate
D2DCryptoOps()59   private D2DCryptoOps() { }
60 
61   /**
62    * Used by the responder device to create a signcrypted message that contains
63    * a payload and a {@link ResponderHello}.
64    *
65    * @param sharedKey used to signcrypt the {@link Payload}
66    * @param publicDhKey the key the recipient will need to derive the shared DH secret.
67    *   This key will be added to the {@link ResponderHello} in the header.
68    * @param protocolVersion the protocol version to include in the proto
69    */
signcryptMessageAndResponderHello( Payload payload, SecretKey sharedKey, PublicKey publicDhKey, int protocolVersion)70   static byte[] signcryptMessageAndResponderHello(
71       Payload payload, SecretKey sharedKey, PublicKey publicDhKey, int protocolVersion)
72       throws InvalidKeyException, NoSuchAlgorithmException {
73     ResponderHello.Builder responderHello = ResponderHello.newBuilder();
74     responderHello.setPublicDhKey(PublicKeyProtoUtil.encodePublicKey(publicDhKey));
75     responderHello.setProtocolVersion(protocolVersion);
76     return signcryptPayload(payload, sharedKey, responderHello.build().toByteArray());
77   }
78 
79   /**
80    * Used by a device to send a secure {@link Payload} to another device.
81    */
signcryptPayload( Payload payload, SecretKey masterKey)82   static byte[] signcryptPayload(
83       Payload payload, SecretKey masterKey)
84       throws InvalidKeyException, NoSuchAlgorithmException {
85     return signcryptPayload(payload, masterKey, null);
86   }
87 
88   /**
89    * Used by a device to send a secure {@link Payload} to another device.
90    *
91    * @param responderHello is an optional public value to attach in the header of
92    *     the {@link SecureMessage} (in the DecryptionKeyId).
93    */
94   @VisibleForTesting
signcryptPayload( Payload payload, SecretKey masterKey, @Nullable byte[] responderHello)95   static byte[] signcryptPayload(
96       Payload payload, SecretKey masterKey, @Nullable byte[] responderHello)
97       throws InvalidKeyException, NoSuchAlgorithmException {
98     if ((payload == null) || (masterKey == null)) {
99       throw new NullPointerException();
100     }
101 
102     SecureMessageBuilder secureMessageBuilder = new SecureMessageBuilder()
103         .setPublicMetadata(GcmMetadata.newBuilder()
104             .setType(payload.getPayloadType().getType())
105             .setVersion(SecureGcmConstants.SECURE_GCM_VERSION)
106             .build()
107             .toByteArray());
108 
109     if (responderHello != null) {
110       secureMessageBuilder.setDecryptionKeyId(responderHello);
111     }
112 
113     return secureMessageBuilder.buildSignCryptedMessage(
114             masterKey,
115             SigType.HMAC_SHA256,
116             masterKey,
117             EncType.AES_256_CBC,
118             payload.getMessage())
119         .toByteArray();
120   }
121 
122   /**
123    * Extracts a ResponderHello proto from the header of a signcrypted message so that we
124    * can derive the shared secret that was used to sign/encrypt the message.
125    *
126    * @return the {@link ResponderHello} embedded in the signcrypted message.
127    */
parseAndValidateResponderHello( byte[] signcryptedMessageFromResponder)128   static ResponderHello parseAndValidateResponderHello(
129       byte[] signcryptedMessageFromResponder) throws InvalidProtocolBufferException {
130     if (signcryptedMessageFromResponder == null) {
131       throw new NullPointerException();
132     }
133     SecureMessage secmsg = SecureMessage.parseFrom(signcryptedMessageFromResponder);
134     Header messageHeader = SecureMessageParser.getUnverifiedHeader(secmsg);
135     if (!messageHeader.hasDecryptionKeyId()) {
136       // Maybe this should be a different exception type, because in general, it's legal for the
137       // SecureMessage proto to not have the decryption key id, but it's illegal in this protocol.
138       throw new InvalidProtocolBufferException("Missing decryption key id");
139     }
140     byte[] encodedResponderHello = messageHeader.getDecryptionKeyId().toByteArray();
141     ResponderHello responderHello = ResponderHello.parseFrom(encodedResponderHello);
142     if (!responderHello.hasPublicDhKey()) {
143       throw new InvalidProtocolBufferException("Missing public key in responder hello");
144     }
145     return responderHello;
146   }
147 
148   /**
149    * Used by a device to recover a secure {@link Payload} sent by another device.
150    */
verifydecryptPayload( byte[] signcryptedMessage, SecretKey masterKey)151   static Payload verifydecryptPayload(
152       byte[] signcryptedMessage, SecretKey masterKey)
153       throws SignatureException, InvalidKeyException, NoSuchAlgorithmException {
154     if ((signcryptedMessage == null) || (masterKey == null)) {
155       throw new NullPointerException();
156     }
157     try {
158       SecureMessage secmsg = SecureMessage.parseFrom(signcryptedMessage);
159       HeaderAndBody parsed = SecureMessageParser.parseSignCryptedMessage(
160           secmsg,
161           masterKey,
162           SigType.HMAC_SHA256,
163           masterKey,
164           EncType.AES_256_CBC);
165       if (!parsed.getHeader().hasPublicMetadata()) {
166         throw new SignatureException("missing metadata");
167       }
168       GcmMetadata metadata = GcmMetadata.parseFrom(parsed.getHeader().getPublicMetadata());
169       if (metadata.getVersion() > SecureGcmConstants.SECURE_GCM_VERSION) {
170         throw new SignatureException("Unsupported protocol version");
171       }
172       return new Payload(PayloadType.valueOf(metadata.getType()), parsed.getBody().toByteArray());
173     } catch (InvalidProtocolBufferException e) {
174       throw new SignatureException(e);
175     } catch (IllegalArgumentException e) {
176       throw new SignatureException(e);
177     }
178   }
179 
180   /**
181    * Used by the initiator device to derive the shared key from the {@link PrivateKey} in the
182    * {@link D2DHandshakeContext} and the responder's {@link GenericPublicKey} (contained in the
183    * {@link ResponderHello} proto).
184    */
deriveSharedKeyFromGenericPublicKey( PrivateKey ourPrivateKey, GenericPublicKey theirGenericPublicKey)185   static SecretKey deriveSharedKeyFromGenericPublicKey(
186       PrivateKey ourPrivateKey, GenericPublicKey theirGenericPublicKey) throws SignatureException {
187     try {
188       PublicKey theirPublicKey = PublicKeyProtoUtil.parsePublicKey(theirGenericPublicKey);
189       return EnrollmentCryptoOps.doKeyAgreement(ourPrivateKey, theirPublicKey);
190     } catch (InvalidKeySpecException e) {
191       throw new SignatureException(e);
192     } catch (InvalidKeyException e) {
193       throw new SignatureException(e);
194     }
195   }
196 
197   /**
198    * Used to derive a distinct key for each initiator and responder.
199    *
200    * @param masterKey the source key used to derive the new key.
201    * @param purpose a string to make the new key different for each purpose.
202    * @return the derived {@link SecretKey}.
203    */
deriveNewKeyForPurpose(SecretKey masterKey, String purpose)204   static SecretKey deriveNewKeyForPurpose(SecretKey masterKey, String purpose)
205       throws NoSuchAlgorithmException, InvalidKeyException {
206     byte[] info = purpose.getBytes();
207     return KeyEncoding.parseMasterKey(CryptoOps.hkdf(masterKey, SALT, info));
208   }
209 
210   /**
211    * Used by the initiator device to decrypt the first payload portion that was sent in the
212    * {@code responderHelloAndPayload}, and extract the {@link DeviceToDeviceMessage} contained
213    * within it. In order to decrypt, the {@code sharedKey} must first be derived.
214    *
215    * @see #deriveSharedKeyFromGenericPublicKey(PrivateKey, GenericPublicKey)
216    */
decryptResponderHelloMessage( SecretKey sharedKey, byte[] responderHelloAndPayload)217   static DeviceToDeviceMessage decryptResponderHelloMessage(
218       SecretKey sharedKey, byte[] responderHelloAndPayload) throws SignatureException {
219     try {
220       Payload payload = verifydecryptPayload(responderHelloAndPayload, sharedKey);
221       if (!PayloadType.DEVICE_TO_DEVICE_RESPONDER_HELLO_PAYLOAD.equals(
222           payload.getPayloadType())) {
223         throw new SignatureException("wrong message type in responder hello");
224       }
225       return DeviceToDeviceMessage.parseFrom(payload.getMessage());
226     } catch (InvalidProtocolBufferException e) {
227       throw new SignatureException(e);
228     } catch (InvalidKeyException e) {
229       throw new SignatureException(e);
230     } catch (NoSuchAlgorithmException e) {
231       throw new SignatureException(e);
232     }
233   }
234 }
235