• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 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   // Data passed to hkdf to create the key used by the initiator to encode messages.
59   static final String INITIATOR_PURPOSE = "initiator";
60   // Data passed to hkdf to create the key used by the responder to encode messages.
61   static final String RESPONDER_PURPOSE = "responder";
62 
63   // Don't instantiate
D2DCryptoOps()64   private D2DCryptoOps() { }
65 
66   /**
67    * Used by the responder device to create a signcrypted message that contains
68    * a payload and a {@link ResponderHello}.
69    *
70    * @param sharedKey used to signcrypt the {@link Payload}
71    * @param publicDhKey the key the recipient will need to derive the shared DH secret.
72    *   This key will be added to the {@link ResponderHello} in the header.
73    * @param protocolVersion the protocol version to include in the proto
74    */
signcryptMessageAndResponderHello( Payload payload, SecretKey sharedKey, PublicKey publicDhKey, int protocolVersion)75   static byte[] signcryptMessageAndResponderHello(
76       Payload payload, SecretKey sharedKey, PublicKey publicDhKey, int protocolVersion)
77       throws InvalidKeyException, NoSuchAlgorithmException {
78     ResponderHello.Builder responderHello = ResponderHello.newBuilder();
79     responderHello.setPublicDhKey(PublicKeyProtoUtil.encodePublicKey(publicDhKey));
80     responderHello.setProtocolVersion(protocolVersion);
81     return signcryptPayload(payload, sharedKey, responderHello.build().toByteArray());
82   }
83 
84   /**
85    * Used by a device to send a secure {@link Payload} to another device.
86    */
signcryptPayload( Payload payload, SecretKey masterKey)87   static byte[] signcryptPayload(
88       Payload payload, SecretKey masterKey)
89       throws InvalidKeyException, NoSuchAlgorithmException {
90     return signcryptPayload(payload, masterKey, null);
91   }
92 
93   /**
94    * Used by a device to send a secure {@link Payload} to another device.
95    *
96    * @param responderHello is an optional public value to attach in the header of
97    *     the {@link SecureMessage} (in the DecryptionKeyId).
98    */
99   @VisibleForTesting
signcryptPayload( Payload payload, SecretKey masterKey, @Nullable byte[] responderHello)100   static byte[] signcryptPayload(
101       Payload payload, SecretKey masterKey, @Nullable byte[] responderHello)
102       throws InvalidKeyException, NoSuchAlgorithmException {
103     if ((payload == null) || (masterKey == null)) {
104       throw new NullPointerException();
105     }
106 
107     SecureMessageBuilder secureMessageBuilder = new SecureMessageBuilder()
108         .setPublicMetadata(GcmMetadata.newBuilder()
109             .setType(payload.getPayloadType().getType())
110             .setVersion(SecureGcmConstants.SECURE_GCM_VERSION)
111             .build()
112             .toByteArray());
113 
114     if (responderHello != null) {
115       secureMessageBuilder.setDecryptionKeyId(responderHello);
116     }
117 
118     return secureMessageBuilder.buildSignCryptedMessage(
119             masterKey,
120             SigType.HMAC_SHA256,
121             masterKey,
122             EncType.AES_256_CBC,
123             payload.getMessage())
124         .toByteArray();
125   }
126 
127   /**
128    * Extracts a ResponderHello proto from the header of a signcrypted message so that we
129    * can derive the shared secret that was used to sign/encrypt the message.
130    *
131    * @return the {@link ResponderHello} embedded in the signcrypted message.
132    */
parseAndValidateResponderHello( byte[] signcryptedMessageFromResponder)133   static ResponderHello parseAndValidateResponderHello(
134       byte[] signcryptedMessageFromResponder) throws InvalidProtocolBufferException {
135     if (signcryptedMessageFromResponder == null) {
136       throw new NullPointerException();
137     }
138     SecureMessage secmsg = SecureMessage.parseFrom(signcryptedMessageFromResponder);
139     Header messageHeader = SecureMessageParser.getUnverifiedHeader(secmsg);
140     if (!messageHeader.hasDecryptionKeyId()) {
141       // Maybe this should be a different exception type, because in general, it's legal for the
142       // SecureMessage proto to not have the decryption key id, but it's illegal in this protocol.
143       throw new InvalidProtocolBufferException("Missing decryption key id");
144     }
145     byte[] encodedResponderHello = messageHeader.getDecryptionKeyId().toByteArray();
146     ResponderHello responderHello = ResponderHello.parseFrom(encodedResponderHello);
147     if (!responderHello.hasPublicDhKey()) {
148       throw new InvalidProtocolBufferException("Missing public key in responder hello");
149     }
150     return responderHello;
151   }
152 
153   /**
154    * Used by a device to recover a secure {@link Payload} sent by another device.
155    */
verifydecryptPayload( byte[] signcryptedMessage, SecretKey masterKey)156   static Payload verifydecryptPayload(
157       byte[] signcryptedMessage, SecretKey masterKey)
158       throws SignatureException, InvalidKeyException, NoSuchAlgorithmException {
159     if ((signcryptedMessage == null) || (masterKey == null)) {
160       throw new NullPointerException();
161     }
162     try {
163       SecureMessage secmsg = SecureMessage.parseFrom(signcryptedMessage);
164       HeaderAndBody parsed = SecureMessageParser.parseSignCryptedMessage(
165           secmsg,
166           masterKey,
167           SigType.HMAC_SHA256,
168           masterKey,
169           EncType.AES_256_CBC);
170       if (!parsed.getHeader().hasPublicMetadata()) {
171         throw new SignatureException("missing metadata");
172       }
173       GcmMetadata metadata = GcmMetadata.parseFrom(parsed.getHeader().getPublicMetadata());
174       if (metadata.getVersion() > SecureGcmConstants.SECURE_GCM_VERSION) {
175         throw new SignatureException("Unsupported protocol version");
176       }
177       return new Payload(PayloadType.valueOf(metadata.getType()), parsed.getBody().toByteArray());
178     } catch (InvalidProtocolBufferException e) {
179       throw new SignatureException(e);
180     } catch (IllegalArgumentException e) {
181       throw new SignatureException(e);
182     }
183   }
184 
185   /**
186    * Used by the initiator device to derive the shared key from the {@link PrivateKey} in the
187    * {@link D2DHandshakeContext} and the responder's {@link GenericPublicKey} (contained in the
188    * {@link ResponderHello} proto).
189    */
deriveSharedKeyFromGenericPublicKey( PrivateKey ourPrivateKey, GenericPublicKey theirGenericPublicKey)190   static SecretKey deriveSharedKeyFromGenericPublicKey(
191       PrivateKey ourPrivateKey, GenericPublicKey theirGenericPublicKey) throws SignatureException {
192     try {
193       PublicKey theirPublicKey = PublicKeyProtoUtil.parsePublicKey(theirGenericPublicKey);
194       return EnrollmentCryptoOps.doKeyAgreement(ourPrivateKey, theirPublicKey);
195     } catch (InvalidKeySpecException e) {
196       throw new SignatureException(e);
197     } catch (InvalidKeyException e) {
198       throw new SignatureException(e);
199     }
200   }
201 
202   /**
203    * Used to derive a distinct key for each initiator and responder.
204    *
205    * @param masterKey the source key used to derive the new key.
206    * @param purpose a string to make the new key different for each purpose.
207    * @return the derived {@link SecretKey}.
208    */
deriveNewKeyForPurpose(SecretKey masterKey, String purpose)209   static SecretKey deriveNewKeyForPurpose(SecretKey masterKey, String purpose)
210       throws NoSuchAlgorithmException, InvalidKeyException {
211     byte[] info = purpose.getBytes();
212     return KeyEncoding.parseMasterKey(CryptoOps.hkdf(masterKey, SALT, info));
213   }
214 
215   /**
216    * Used by the initiator device to decrypt the first payload portion that was sent in the
217    * {@code responderHelloAndPayload}, and extract the {@link DeviceToDeviceMessage} contained
218    * within it. In order to decrypt, the {@code sharedKey} must first be derived.
219    *
220    * @see #deriveSharedKeyFromGenericPublicKey(PrivateKey, GenericPublicKey)
221    */
decryptResponderHelloMessage( SecretKey sharedKey, byte[] responderHelloAndPayload)222   static DeviceToDeviceMessage decryptResponderHelloMessage(
223       SecretKey sharedKey, byte[] responderHelloAndPayload) throws SignatureException {
224     try {
225       Payload payload = verifydecryptPayload(responderHelloAndPayload, sharedKey);
226       if (!PayloadType.DEVICE_TO_DEVICE_RESPONDER_HELLO_PAYLOAD.equals(
227           payload.getPayloadType())) {
228         throw new SignatureException("wrong message type in responder hello");
229       }
230       return DeviceToDeviceMessage.parseFrom(payload.getMessage());
231     } catch (InvalidProtocolBufferException e) {
232       throw new SignatureException(e);
233     } catch (InvalidKeyException e) {
234       throw new SignatureException(e);
235     } catch (NoSuchAlgorithmException e) {
236       throw new SignatureException(e);
237     }
238   }
239 }
240