Home | History | Annotate | Download | only in securegcm
      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
     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    */
     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    */
     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
     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    */
    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    */
    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    */
    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    */
    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    */
    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