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.protobuf.InvalidProtocolBufferException;
     18 import com.google.security.cryptauth.lib.securegcm.SecureGcmProto.GcmMetadata;
     19 import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType;
     20 import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType;
     21 import com.google.security.cryptauth.lib.securemessage.SecureMessageBuilder;
     22 import com.google.security.cryptauth.lib.securemessage.SecureMessageParser;
     23 import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBody;
     24 import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage;
     25 import java.security.InvalidKeyException;
     26 import java.security.KeyPair;
     27 import java.security.NoSuchAlgorithmException;
     28 import java.security.PrivateKey;
     29 import java.security.PublicKey;
     30 import java.security.SignatureException;
     31 import java.security.interfaces.ECPublicKey;
     32 import java.security.interfaces.RSAPublicKey;
     33 import javax.crypto.SecretKey;
     34 
     35 /**
     36  * Utility class for implementing a secure transport for GCM messages.
     37  */
     38 public class TransportCryptoOps {
     39   private TransportCryptoOps() {} // Do not instantiate
     40 
     41   /**
     42    * A type safe version of the {@link SecureGcmProto} {@code Type} codes.
     43    */
     44   public enum PayloadType {
     45     ENROLLMENT(SecureGcmProto.Type.ENROLLMENT),
     46     TICKLE(SecureGcmProto.Type.TICKLE),
     47     TX_REQUEST(SecureGcmProto.Type.TX_REQUEST),
     48     TX_REPLY(SecureGcmProto.Type.TX_REPLY),
     49     TX_SYNC_REQUEST(SecureGcmProto.Type.TX_SYNC_REQUEST),
     50     TX_SYNC_RESPONSE(SecureGcmProto.Type.TX_SYNC_RESPONSE),
     51     TX_PING(SecureGcmProto.Type.TX_PING),
     52     DEVICE_INFO_UPDATE(SecureGcmProto.Type.DEVICE_INFO_UPDATE),
     53     TX_CANCEL_REQUEST(SecureGcmProto.Type.TX_CANCEL_REQUEST),
     54     LOGIN_NOTIFICATION(SecureGcmProto.Type.LOGIN_NOTIFICATION),
     55     PROXIMITYAUTH_PAIRING(SecureGcmProto.Type.PROXIMITYAUTH_PAIRING),
     56     GCMV1_IDENTITY_ASSERTION(SecureGcmProto.Type.GCMV1_IDENTITY_ASSERTION),
     57     DEVICE_TO_DEVICE_RESPONDER_HELLO_PAYLOAD(
     58         SecureGcmProto.Type.DEVICE_TO_DEVICE_RESPONDER_HELLO_PAYLOAD),
     59     DEVICE_TO_DEVICE_MESSAGE(SecureGcmProto.Type.DEVICE_TO_DEVICE_MESSAGE),
     60     DEVICE_PROXIMITY_CALLBACK(SecureGcmProto.Type.DEVICE_PROXIMITY_CALLBACK),
     61     UNLOCK_KEY_SIGNED_CHALLENGE(SecureGcmProto.Type.UNLOCK_KEY_SIGNED_CHALLENGE);
     62 
     63     private final SecureGcmProto.Type type;
     64     PayloadType(SecureGcmProto.Type type) {
     65       this.type = type;
     66     }
     67 
     68     public SecureGcmProto.Type getType() {
     69       return this.type;
     70     }
     71 
     72     public static PayloadType valueOf(SecureGcmProto.Type type) {
     73       return PayloadType.valueOf(type.getNumber());
     74     }
     75 
     76     public static PayloadType valueOf(int type) {
     77       for (PayloadType payloadType : PayloadType.values()) {
     78         if (payloadType.getType().getNumber() == type) {
     79           return payloadType;
     80         }
     81       }
     82       throw new IllegalArgumentException("Unsupported payload type: " + type);
     83     }
     84   }
     85 
     86   /**
     87    * Encapsulates a {@link PayloadType} specifier, and a corresponding raw {@code message} payload.
     88    */
     89   public static class Payload {
     90     private final PayloadType payloadType;
     91     private final byte[] message;
     92 
     93     public Payload(PayloadType payloadType, byte[] message) {
     94       if ((payloadType == null) || (message == null)) {
     95         throw new NullPointerException();
     96       }
     97       this.payloadType = payloadType;
     98       this.message = message;
     99     }
    100 
    101     public PayloadType getPayloadType() {
    102       return payloadType;
    103     }
    104 
    105     public byte[] getMessage() {
    106       return message;
    107     }
    108   }
    109 
    110   /**
    111    * Used by the the server-side to send a secure {@link Payload} to the client.
    112    *
    113    * @param masterKey used to signcrypt the {@link Payload}
    114    * @param keyHandle the name by which the client refers to the specified {@code masterKey}
    115    */
    116   public static byte[] signcryptServerMessage(
    117       Payload payload, SecretKey masterKey, byte[] keyHandle)
    118       throws InvalidKeyException, NoSuchAlgorithmException {
    119     if ((payload == null) || (masterKey == null) || (keyHandle == null)) {
    120       throw new NullPointerException();
    121     }
    122     return new SecureMessageBuilder()
    123         .setVerificationKeyId(keyHandle)
    124         .setPublicMetadata(GcmMetadata.newBuilder()
    125             .setType(payload.getPayloadType().getType())
    126             .setVersion(SecureGcmConstants.SECURE_GCM_VERSION)
    127             .build()
    128             .toByteArray())
    129         .buildSignCryptedMessage(
    130             masterKey,
    131             SigType.HMAC_SHA256,
    132             masterKey,
    133             EncType.AES_256_CBC,
    134             payload.getMessage())
    135         .toByteArray();
    136   }
    137 
    138   /**
    139    * Extracts the {@code keyHandle} from a {@code signcryptedMessage}.
    140    *
    141    * @see #signcryptServerMessage(Payload, SecretKey, byte[])
    142    */
    143   public static byte[] getKeyHandleFor(byte[] signcryptedServerMessage)
    144       throws InvalidProtocolBufferException  {
    145     if (signcryptedServerMessage == null) {
    146       throw new NullPointerException();
    147     }
    148     SecureMessage secmsg = SecureMessage.parseFrom(signcryptedServerMessage);
    149     return SecureMessageParser.getUnverifiedHeader(secmsg).getVerificationKeyId().toByteArray();
    150   }
    151 
    152   /**
    153    * Used by a client to recover a secure {@link Payload} sent by the server-side.
    154    *
    155    * @see #getKeyHandleFor(byte[])
    156    * @see #signcryptServerMessage(Payload, SecretKey, byte[])
    157    */
    158   public static Payload verifydecryptServerMessage(
    159       byte[] signcryptedServerMessage, SecretKey masterKey)
    160       throws SignatureException, InvalidKeyException, NoSuchAlgorithmException {
    161     if ((signcryptedServerMessage == null) || (masterKey == null)) {
    162       throw new NullPointerException();
    163     }
    164     try {
    165       SecureMessage secmsg = SecureMessage.parseFrom(signcryptedServerMessage);
    166       HeaderAndBody parsed = SecureMessageParser.parseSignCryptedMessage(
    167           secmsg,
    168           masterKey,
    169           SigType.HMAC_SHA256,
    170           masterKey,
    171           EncType.AES_256_CBC);
    172       GcmMetadata metadata = GcmMetadata.parseFrom(parsed.getHeader().getPublicMetadata());
    173       if (metadata.getVersion() > SecureGcmConstants.SECURE_GCM_VERSION) {
    174         throw new SignatureException("Unsupported protocol version");
    175       }
    176       return new Payload(PayloadType.valueOf(metadata.getType()), parsed.getBody().toByteArray());
    177     } catch (InvalidProtocolBufferException | IllegalArgumentException e) {
    178       throw new SignatureException(e);
    179     }
    180   }
    181 
    182   /**
    183    * Used by the the client-side to send a secure {@link Payload} to the client.
    184    *
    185    * @param userKeyPair used to sign the {@link Payload}. In particular, the {@link PrivateKey}
    186    *   portion is used for signing, and (the {@link PublicKey} portion is sent to the server.
    187    * @param masterKey used to encrypt the {@link Payload}
    188    */
    189   public static byte[] signcryptClientMessage(
    190       Payload payload, KeyPair userKeyPair, SecretKey masterKey)
    191       throws InvalidKeyException, NoSuchAlgorithmException {
    192     if ((payload == null) || (masterKey == null)) {
    193       throw new NullPointerException();
    194     }
    195 
    196     PublicKey userPublicKey = userKeyPair.getPublic();
    197     PrivateKey userPrivateKey = userKeyPair.getPrivate();
    198 
    199     return new SecureMessageBuilder()
    200         .setVerificationKeyId(KeyEncoding.encodeUserPublicKey(userPublicKey))
    201         .setPublicMetadata(GcmMetadata.newBuilder()
    202             .setType(payload.getPayloadType().getType())
    203             .setVersion(SecureGcmConstants.SECURE_GCM_VERSION)
    204             .build()
    205             .toByteArray())
    206         .buildSignCryptedMessage(
    207             userPrivateKey,
    208             getSigTypeFor(userPublicKey),
    209             masterKey,
    210             EncType.AES_256_CBC,
    211             payload.getMessage())
    212         .toByteArray();
    213   }
    214 
    215   /**
    216    * Used by the server-side to recover a secure {@link Payload} sent by a client.
    217    *
    218    * @see #getEncodedUserPublicKeyFor(byte[])
    219    * @see #signcryptClientMessage(Payload, KeyPair, SecretKey)
    220    */
    221   public static Payload verifydecryptClientMessage(
    222       byte[] signcryptedClientMessage, PublicKey userPublicKey, SecretKey masterKey)
    223       throws SignatureException, InvalidKeyException, NoSuchAlgorithmException {
    224     if ((signcryptedClientMessage == null) || (masterKey == null)) {
    225       throw new NullPointerException();
    226     }
    227     try {
    228       SecureMessage secmsg = SecureMessage.parseFrom(signcryptedClientMessage);
    229       HeaderAndBody parsed = SecureMessageParser.parseSignCryptedMessage(
    230           secmsg,
    231           userPublicKey,
    232           getSigTypeFor(userPublicKey),
    233           masterKey,
    234           EncType.AES_256_CBC);
    235       GcmMetadata metadata = GcmMetadata.parseFrom(parsed.getHeader().getPublicMetadata());
    236       if (metadata.getVersion() > SecureGcmConstants.SECURE_GCM_VERSION) {
    237         throw new SignatureException("Unsupported protocol version");
    238       }
    239       return new Payload(PayloadType.valueOf(metadata.getType()), parsed.getBody().toByteArray());
    240     } catch (InvalidProtocolBufferException | IllegalArgumentException e) {
    241       throw new SignatureException(e);
    242     }
    243   }
    244 
    245   /**
    246    * Extracts an encoded {@code userPublicKey} from a {@code signcryptedClientMessage}.
    247    *
    248    * @see #signcryptClientMessage(Payload, KeyPair, SecretKey)
    249    */
    250   public static byte[] getEncodedUserPublicKeyFor(byte[] signcryptedClientMessage)
    251       throws InvalidProtocolBufferException  {
    252     if (signcryptedClientMessage == null) {
    253       throw new NullPointerException();
    254     }
    255     SecureMessage secmsg = SecureMessage.parseFrom(signcryptedClientMessage);
    256     return SecureMessageParser.getUnverifiedHeader(secmsg).getVerificationKeyId().toByteArray();
    257   }
    258 
    259   private static SigType getSigTypeFor(PublicKey userPublicKey) throws InvalidKeyException {
    260     if (userPublicKey instanceof ECPublicKey) {
    261       return SigType.ECDSA_P256_SHA256;
    262     } else if (userPublicKey instanceof RSAPublicKey) {
    263       return SigType.RSA2048_SHA256;
    264     } else {
    265       throw new InvalidKeyException("Unsupported key type");
    266     }
    267   }
    268 }
    269