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