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