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 { 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; PayloadType(SecureGcmProto.Type type)64 PayloadType(SecureGcmProto.Type type) { 65 this.type = type; 66 } 67 getType()68 public SecureGcmProto.Type getType() { 69 return this.type; 70 } 71 valueOf(SecureGcmProto.Type type)72 public static PayloadType valueOf(SecureGcmProto.Type type) { 73 return PayloadType.valueOf(type.getNumber()); 74 } 75 valueOf(int type)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 Payload(PayloadType payloadType, byte[] message)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 getPayloadType()101 public PayloadType getPayloadType() { 102 return payloadType; 103 } 104 getMessage()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 */ signcryptServerMessage( Payload payload, SecretKey masterKey, byte[] keyHandle)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 */ getKeyHandleFor(byte[] signcryptedServerMessage)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 */ verifydecryptServerMessage( byte[] signcryptedServerMessage, SecretKey masterKey)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 */ signcryptClientMessage( Payload payload, KeyPair userKeyPair, SecretKey masterKey)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 */ verifydecryptClientMessage( byte[] signcryptedClientMessage, PublicKey userPublicKey, SecretKey masterKey)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 */ getEncodedUserPublicKeyFor(byte[] signcryptedClientMessage)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 getSigTypeFor(PublicKey userPublicKey)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