Add support for kyber prekeys.

This commit is contained in:
Greyson Parrelli
2023-05-22 14:03:31 -07:00
committed by Cody Henthorne
parent 15c248184f
commit e2ef8e2ef9
24 changed files with 669 additions and 208 deletions

View File

@@ -6,7 +6,10 @@ import org.signal.libsignal.protocol.state.SignalProtocolStore;
* And extension of the normal protocol store interface that has additional methods that are needed
* in the service layer, but not the protocol layer.
*/
public interface SignalServiceAccountDataStore extends SignalProtocolStore, SignalServiceSessionStore, SignalServiceSenderKeyStore {
public interface SignalServiceAccountDataStore extends SignalProtocolStore,
SignalServiceSessionStore,
SignalServiceSenderKeyStore,
SignalServiceKyberPreKeyStore {
/**
* @return True if the user has linked devices, otherwise false.
*/

View File

@@ -9,17 +9,16 @@ package org.whispersystems.signalservice.api;
import com.google.protobuf.ByteString;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.IdentityKeyPair;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.ecc.ECPublicKey;
import org.signal.libsignal.protocol.logging.Log;
import org.signal.libsignal.protocol.state.PreKeyRecord;
import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.whispersystems.signalservice.api.account.AccountAttributes;
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest;
import org.whispersystems.signalservice.api.account.PreKeyUpload;
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
import org.whispersystems.signalservice.api.crypto.ProfileCipherOutputStream;
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
@@ -36,7 +35,6 @@ import org.whispersystems.signalservice.api.push.ACI;
import org.whispersystems.signalservice.api.push.PNI;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceIdType;
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity;
import org.whispersystems.signalservice.api.push.exceptions.NoContentException;
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
@@ -59,6 +57,7 @@ import org.whispersystems.signalservice.internal.push.AuthCredentials;
import org.whispersystems.signalservice.internal.push.BackupAuthCheckRequest;
import org.whispersystems.signalservice.internal.push.BackupAuthCheckResponse;
import org.whispersystems.signalservice.internal.push.CdsiAuthResponse;
import org.whispersystems.signalservice.internal.push.OneTimePreKeyCounts;
import org.whispersystems.signalservice.internal.push.ProfileAvatarData;
import org.whispersystems.signalservice.internal.push.PushServiceSocket;
import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse;
@@ -80,7 +79,6 @@ import org.whispersystems.signalservice.internal.websocket.DefaultResponseMapper
import org.whispersystems.util.Base64;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -98,7 +96,6 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -351,23 +348,19 @@ public class SignalServiceAccountManager {
* Register an identity key, signed prekey, and list of one time prekeys
* with the server.
*
* @param identityKey The client's long-term identity keypair.
* @param signedPreKey The client's signed prekey.
* @param oneTimePreKeys The client's list of one-time prekeys.
*
* @throws IOException
*/
public void setPreKeys(ServiceIdType serviceIdType, IdentityKey identityKey, SignedPreKeyRecord signedPreKey, List<PreKeyRecord> oneTimePreKeys)
public void setPreKeys(PreKeyUpload preKeyUpload)
throws IOException
{
this.pushServiceSocket.registerPreKeys(serviceIdType, identityKey, signedPreKey, oneTimePreKeys);
this.pushServiceSocket.registerPreKeys(preKeyUpload);
}
/**
* @return The server's count of currently available (eg. unused) prekeys for this user.
* @throws IOException
*/
public int getPreKeysCount(ServiceIdType serviceIdType) throws IOException {
public OneTimePreKeyCounts getPreKeyCounts(ServiceIdType serviceIdType) throws IOException {
return this.pushServiceSocket.getAvailablePreKeys(serviceIdType);
}
@@ -381,14 +374,6 @@ public class SignalServiceAccountManager {
this.pushServiceSocket.setCurrentSignedPreKey(serviceIdType, signedPreKey);
}
/**
* @return The server's view of the client's current signed prekey.
* @throws IOException
*/
public SignedPreKeyEntity getSignedPreKey(ServiceIdType serviceIdType) throws IOException {
return this.pushServiceSocket.getCurrentSignedPreKey(serviceIdType);
}
/**
* @return True if the identifier corresponds to a registered user, otherwise false.
*/

View File

@@ -0,0 +1,26 @@
package org.whispersystems.signalservice.api
import org.signal.libsignal.protocol.state.KyberPreKeyRecord
import org.signal.libsignal.protocol.state.KyberPreKeyStore
/**
* And extension of the normal protocol sender key store interface that has additional methods that are
* needed in the service layer, but not the protocol layer.
*/
interface SignalServiceKyberPreKeyStore : KyberPreKeyStore {
/**
* Identical to [storeKyberPreKey] but indicates that this is a last-resort key rather than a one-time key.
*/
fun storeLastResortKyberPreKey(kyberPreKeyId: Int, kyberPreKeyRecord: KyberPreKeyRecord)
/**
* Retrieves all last-resort kyber prekeys.
*/
fun loadLastResortKyberPreKeys(): List<KyberPreKeyRecord>
/**
* Unconditionally remove the specified key from the store.
*/
fun removeKyberPreKey(kyberPreKeyId: Int)
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.account
import org.signal.libsignal.protocol.IdentityKey
import org.signal.libsignal.protocol.state.KyberPreKeyRecord
import org.signal.libsignal.protocol.state.PreKeyRecord
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
import org.whispersystems.signalservice.api.push.ServiceIdType
/**
* Represents a bundle of prekeys you want to upload.
*
* If a field is nullable, not setting it will simply leave that field alone on the service.
*/
data class PreKeyUpload(
val serviceIdType: ServiceIdType,
val identityKey: IdentityKey,
val signedPreKey: SignedPreKeyRecord?,
val oneTimeEcPreKeys: List<PreKeyRecord>?,
val lastResortKyberPreKey: KyberPreKeyRecord?,
val oneTimeKyberPreKeys: List<KyberPreKeyRecord>?
)

View File

@@ -0,0 +1,87 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.whispersystems.signalservice.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.signal.libsignal.protocol.kem.KEMPublicKey;
import org.whispersystems.util.Base64;
import java.io.IOException;
public class KyberPreKeyEntity {
@JsonProperty
private int keyId;
@JsonProperty
@JsonSerialize(using = KEMPublicKeySerializer.class)
@JsonDeserialize(using = KEMPublicKeyDeserializer.class)
private KEMPublicKey publicKey;
@JsonProperty
@JsonSerialize(using = ByteArraySerializer.class)
@JsonDeserialize(using = ByteArrayDeserializer.class)
private byte[] signature;
public KyberPreKeyEntity() {}
public KyberPreKeyEntity(int keyId, KEMPublicKey publicKey, byte[] signature) {
this.keyId = keyId;
this.publicKey = publicKey;
this.signature = signature;
}
public int getKeyId() {
return keyId;
}
public KEMPublicKey getPublicKey() {
return publicKey;
}
public byte[] getSignature() {
return signature;
}
private static class KEMPublicKeySerializer extends JsonSerializer<KEMPublicKey> {
@Override
public void serialize(KEMPublicKey value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(Base64.encodeBytesWithoutPadding(value.serialize()));
}
}
private static class KEMPublicKeyDeserializer extends JsonDeserializer<KEMPublicKey> {
@Override
public KEMPublicKey deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return new KEMPublicKey(Base64.decodeWithoutPadding(p.getValueAsString()), 0);
}
}
private static class ByteArraySerializer extends JsonSerializer<byte[]> {
@Override
public void serialize(byte[] value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(Base64.encodeBytesWithoutPadding(value));
}
}
private static class ByteArrayDeserializer extends JsonDeserializer<byte[]> {
@Override
public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return Base64.decodeWithoutPadding(p.getValueAsString());
}
}
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.whispersystems.signalservice.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
public class OneTimePreKeyCounts {
@JsonProperty("count")
private int ecCount;
@JsonProperty("pqCount")
private int kyberCount;
public OneTimePreKeyCounts() {}
public OneTimePreKeyCounts(int ecCount, int kyberCount) {
this.ecCount = ecCount;
this.kyberCount = kyberCount;
}
public int getEcCount() {
return ecCount;
}
public int getKyberCount() {
return kyberCount;
}
}

View File

@@ -1,6 +1,6 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* <p>
* Licensed according to the LICENSE file in this repository.
*/
@@ -13,16 +13,19 @@ import org.whispersystems.signalservice.api.push.SignedPreKeyEntity;
public class PreKeyResponseItem {
@JsonProperty
public int deviceId;
public int deviceId;
@JsonProperty
public int registrationId;
public int registrationId;
@JsonProperty
public SignedPreKeyEntity signedPreKey;
@JsonProperty
public PreKeyEntity preKey;
public PreKeyEntity preKey;
@JsonProperty("pqPreKey")
public KyberPreKeyEntity kyberPreKey;
public int getDeviceId() {
return deviceId;
@@ -40,4 +43,8 @@ public class PreKeyResponseItem {
return preKey;
}
public KyberPreKeyEntity getKyberPreKey() {
return kyberPreKey;
}
}

View File

@@ -12,23 +12,37 @@ import java.util.List;
public class PreKeyState {
@JsonProperty
@JsonProperty("identityKey")
@JsonSerialize(using = JsonUtil.IdentityKeySerializer.class)
@JsonDeserialize(using = JsonUtil.IdentityKeyDeserializer.class)
private IdentityKey identityKey;
@JsonProperty
private List<PreKeyEntity> preKeys;
@JsonProperty("preKeys")
private List<PreKeyEntity> oneTimeEcPreKeys;
@JsonProperty
@JsonProperty("signedPreKey")
private SignedPreKeyEntity signedPreKey;
@JsonProperty("pqLastResortPreKey")
private KyberPreKeyEntity lastResortKyberKey;
@JsonProperty("pqPreKeys")
private List<KyberPreKeyEntity> oneTimeKyberKeys;
public PreKeyState() {}
public PreKeyState(List<PreKeyEntity> preKeys, SignedPreKeyEntity signedPreKey, IdentityKey identityKey) {
this.preKeys = preKeys;
this.signedPreKey = signedPreKey;
this.identityKey = identityKey;
public PreKeyState(
IdentityKey identityKey,
SignedPreKeyEntity signedPreKey,
List<PreKeyEntity> oneTimeEcPreKeys,
KyberPreKeyEntity lastResortKyberPreKey,
List<KyberPreKeyEntity> oneTimeKyberPreKeys
) {
this.identityKey = identityKey;
this.signedPreKey = signedPreKey;
this.oneTimeEcPreKeys = oneTimeEcPreKeys;
this.lastResortKyberKey = lastResortKyberPreKey;
this.oneTimeKyberKeys = oneTimeKyberPreKeys;
}
public IdentityKey getIdentityKey() {
@@ -36,7 +50,7 @@ public class PreKeyState {
}
public List<PreKeyEntity> getPreKeys() {
return preKeys;
return oneTimeEcPreKeys;
}
public SignedPreKeyEntity getSignedPreKey() {

View File

@@ -1,25 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.whispersystems.signalservice.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
public class PreKeyStatus {
@JsonProperty
private int count;
public PreKeyStatus() {}
public PreKeyStatus(int count) {
this.count = count;
}
public int getCount() {
return count;
}
}

View File

@@ -11,12 +11,11 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.MessageLite;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.ecc.ECPublicKey;
import org.signal.libsignal.protocol.kem.KEMPublicKey;
import org.signal.libsignal.protocol.logging.Log;
import org.signal.libsignal.protocol.state.PreKeyBundle;
import org.signal.libsignal.protocol.state.PreKeyRecord;
import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
import org.signal.libsignal.protocol.util.Pair;
import org.signal.libsignal.usernames.BaseUsernameException;
@@ -41,6 +40,7 @@ import org.signal.storageservice.protos.groups.GroupExternalCredential;
import org.signal.storageservice.protos.groups.GroupJoinInfo;
import org.whispersystems.signalservice.api.account.AccountAttributes;
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest;
import org.whispersystems.signalservice.api.account.PreKeyUpload;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
import org.whispersystems.signalservice.api.groupsv2.CredentialResponse;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
@@ -165,6 +165,7 @@ import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -215,8 +216,8 @@ public class PushServiceSocket {
private static final String REQUEST_ACCOUNT_DATA_PATH = "/v2/accounts/data_report";
private static final String PREKEY_METADATA_PATH = "/v2/keys?identity=%s";
private static final String PREKEY_PATH = "/v2/keys/%s?identity=%s";
private static final String PREKEY_DEVICE_PATH = "/v2/keys/%s/%s";
private static final String PREKEY_PATH = "/v2/keys?identity=%s";
private static final String PREKEY_DEVICE_PATH = "/v2/keys/%s/%s?pq=true";
private static final String SIGNED_PREKEY_PATH = "/v2/keys/signed?identity=%s";
private static final String PROVISIONING_CODE_PATH = "/v1/devices/provisioning/code";
@@ -612,67 +613,113 @@ public class PushServiceSocket {
makeServiceRequest(String.format(UUID_ACK_MESSAGE_PATH, uuid), "DELETE", null);
}
public void registerPreKeys(ServiceIdType serviceIdType,
IdentityKey identityKey,
SignedPreKeyRecord signedPreKey,
List<PreKeyRecord> records)
public void registerPreKeys(PreKeyUpload preKeyUpload)
throws IOException
{
List<PreKeyEntity> entities = new LinkedList<>();
SignedPreKeyEntity signedPreKey = null;
List<PreKeyEntity> oneTimeEcPreKeys = null;
KyberPreKeyEntity lastResortKyberPreKey = null;
List<KyberPreKeyEntity> oneTimeKyberPreKeys = null;
try {
for (PreKeyRecord record : records) {
PreKeyEntity entity = new PreKeyEntity(record.getId(),
record.getKeyPair().getPublicKey());
entities.add(entity);
}
} catch (InvalidKeyException e) {
throw new AssertionError("unexpected invalid key", e);
if (preKeyUpload.getSignedPreKey() != null) {
signedPreKey = new SignedPreKeyEntity(preKeyUpload.getSignedPreKey().getId(),
preKeyUpload.getSignedPreKey().getKeyPair().getPublicKey(),
preKeyUpload.getSignedPreKey().getSignature());
}
SignedPreKeyEntity signedPreKeyEntity = new SignedPreKeyEntity(signedPreKey.getId(),
signedPreKey.getKeyPair().getPublicKey(),
signedPreKey.getSignature());
if (preKeyUpload.getOneTimeEcPreKeys() != null) {
oneTimeEcPreKeys = preKeyUpload
.getOneTimeEcPreKeys()
.stream()
.map(it -> {
try {
return new PreKeyEntity(it.getId(), it.getKeyPair().getPublicKey());
} catch (InvalidKeyException e) {
throw new AssertionError("unexpected invalid key", e);
}
})
.collect(Collectors.toList());
}
makeServiceRequest(String.format(Locale.US, PREKEY_PATH, "", serviceIdType.queryParam()),
if (preKeyUpload.getLastResortKyberPreKey() != null) {
lastResortKyberPreKey = new KyberPreKeyEntity(preKeyUpload.getLastResortKyberPreKey().getId(),
preKeyUpload.getLastResortKyberPreKey().getKeyPair().getPublicKey(),
preKeyUpload.getLastResortKyberPreKey().getSignature());
}
if (preKeyUpload.getOneTimeKyberPreKeys() != null) {
oneTimeKyberPreKeys = preKeyUpload
.getOneTimeKyberPreKeys()
.stream()
.map(it -> new KyberPreKeyEntity(it.getId(), it.getKeyPair().getPublicKey(), it.getSignature()))
.collect(Collectors.toList());
}
makeServiceRequest(String.format(Locale.US, PREKEY_PATH, preKeyUpload.getServiceIdType().queryParam()),
"PUT",
JsonUtil.toJson(new PreKeyState(entities, signedPreKeyEntity, identityKey)));
JsonUtil.toJson(new PreKeyState(preKeyUpload.getIdentityKey(),
signedPreKey,
oneTimeEcPreKeys,
lastResortKyberPreKey,
oneTimeKyberPreKeys)));
}
public int getAvailablePreKeys(ServiceIdType serviceIdType) throws IOException {
String path = String.format(PREKEY_METADATA_PATH, serviceIdType.queryParam());
String responseText = makeServiceRequest(path, "GET", null);
PreKeyStatus preKeyStatus = JsonUtil.fromJson(responseText, PreKeyStatus.class);
public OneTimePreKeyCounts getAvailablePreKeys(ServiceIdType serviceIdType) throws IOException {
String path = String.format(PREKEY_METADATA_PATH, serviceIdType.queryParam());
String responseText = makeServiceRequest(path, "GET", null);
OneTimePreKeyCounts preKeyStatus = JsonUtil.fromJson(responseText, OneTimePreKeyCounts.class);
return preKeyStatus.getCount();
return preKeyStatus;
}
/**
* Retrieves prekeys. If the specified device is the primary (i.e. deviceId 1), it will retrieve prekeys
* for all devices. If it is not a primary, it will only contain the prekeys for that specific device.
*/
public List<PreKeyBundle> getPreKeys(SignalServiceAddress destination,
Optional<UnidentifiedAccess> unidentifiedAccess,
int deviceIdInteger)
int deviceId)
throws IOException
{
return getPreKeysBySpecifier(destination, unidentifiedAccess, deviceId == 1 ? "*" : String.valueOf(deviceId));
}
/**
* Retrieves a prekey for a specific device.
*/
public PreKeyBundle getPreKey(SignalServiceAddress destination, int deviceId) throws IOException {
List<PreKeyBundle> bundles = getPreKeysBySpecifier(destination, Optional.empty(), String.valueOf(deviceId));
if (bundles.size() > 0) {
return bundles.get(0);
} else {
throw new IOException("No prekeys available!");
}
}
private List<PreKeyBundle> getPreKeysBySpecifier(SignalServiceAddress destination,
Optional<UnidentifiedAccess> unidentifiedAccess,
String deviceSpecifier)
throws IOException
{
try {
String deviceId = String.valueOf(deviceIdInteger);
String path = String.format(PREKEY_DEVICE_PATH, destination.getIdentifier(), deviceSpecifier);
if (deviceId.equals("1"))
deviceId = "*";
String path = String.format(PREKEY_DEVICE_PATH, destination.getIdentifier(), deviceId);
Log.d(TAG, "Fetching prekeys for " + destination.getIdentifier() + "." + deviceId + ", i.e. GET " + path);
Log.d(TAG, "Fetching prekeys for " + destination.getIdentifier() + "." + deviceSpecifier + ", i.e. GET " + path);
String responseText = makeServiceRequest(path, "GET", null, NO_HEADERS, NO_HANDLER, unidentifiedAccess);
PreKeyResponse response = JsonUtil.fromJson(responseText, PreKeyResponse.class);
List<PreKeyBundle> bundles = new LinkedList<>();
for (PreKeyResponseItem device : response.getDevices()) {
ECPublicKey preKey = null;
ECPublicKey signedPreKey = null;
byte[] signedPreKeySignature = null;
int preKeyId = -1;
int signedPreKeyId = -1;
ECPublicKey preKey = null;
ECPublicKey signedPreKey = null;
byte[] signedPreKeySignature = null;
int preKeyId = PreKeyBundle.NULL_PRE_KEY_ID;
int signedPreKeyId = PreKeyBundle.NULL_PRE_KEY_ID;
int kyberPreKeyId = PreKeyBundle.NULL_PRE_KEY_ID;
KEMPublicKey kyberPreKey = null;
byte[] kyberPreKeySignature = null;
if (device.getSignedPreKey() != null) {
signedPreKey = device.getSignedPreKey().getPublicKey();
@@ -685,9 +732,23 @@ public class PushServiceSocket {
preKey = device.getPreKey().getPublicKey();
}
bundles.add(new PreKeyBundle(device.getRegistrationId(), device.getDeviceId(), preKeyId,
preKey, signedPreKeyId, signedPreKey, signedPreKeySignature,
response.getIdentityKey()));
if (device.getKyberPreKey() != null) {
kyberPreKey = device.getKyberPreKey().getPublicKey();
kyberPreKeyId = device.getKyberPreKey().getKeyId();
kyberPreKeySignature = device.getKyberPreKey().getSignature();
}
bundles.add(new PreKeyBundle(device.getRegistrationId(),
device.getDeviceId(),
preKeyId,
preKey,
signedPreKeyId,
signedPreKey,
signedPreKeySignature,
response.getIdentityKey(),
kyberPreKeyId,
kyberPreKey,
kyberPreKeySignature));
}
return bundles;
@@ -696,52 +757,6 @@ public class PushServiceSocket {
}
}
public PreKeyBundle getPreKey(SignalServiceAddress destination, int deviceId) throws IOException {
try {
String path = String.format(PREKEY_DEVICE_PATH, destination.getIdentifier(), String.valueOf(deviceId));
String responseText = makeServiceRequest(path, "GET", null);
PreKeyResponse response = JsonUtil.fromJson(responseText, PreKeyResponse.class);
if (response.getDevices() == null || response.getDevices().size() < 1)
throw new IOException("Empty prekey list");
PreKeyResponseItem device = response.getDevices().get(0);
ECPublicKey preKey = null;
ECPublicKey signedPreKey = null;
byte[] signedPreKeySignature = null;
int preKeyId = -1;
int signedPreKeyId = -1;
if (device.getPreKey() != null) {
preKeyId = device.getPreKey().getKeyId();
preKey = device.getPreKey().getPublicKey();
}
if (device.getSignedPreKey() != null) {
signedPreKeyId = device.getSignedPreKey().getKeyId();
signedPreKey = device.getSignedPreKey().getPublicKey();
signedPreKeySignature = device.getSignedPreKey().getSignature();
}
return new PreKeyBundle(device.getRegistrationId(), device.getDeviceId(), preKeyId, preKey,
signedPreKeyId, signedPreKey, signedPreKeySignature, response.getIdentityKey());
} catch (NotFoundException nfe) {
throw new UnregisteredUserException(destination.getIdentifier(), nfe);
}
}
public SignedPreKeyEntity getCurrentSignedPreKey(ServiceIdType serviceIdType) throws IOException {
try {
String path = String.format(SIGNED_PREKEY_PATH, serviceIdType.queryParam());
String responseText = makeServiceRequest(path, "GET", null);
return JsonUtil.fromJson(responseText, SignedPreKeyEntity.class);
} catch (NotFoundException e) {
Log.w(TAG, e);
return null;
}
}
public void setCurrentSignedPreKey(ServiceIdType serviceIdType, SignedPreKeyRecord signedPreKey) throws IOException {
String path = String.format(SIGNED_PREKEY_PATH, serviceIdType.queryParam());
SignedPreKeyEntity signedPreKeyEntity = new SignedPreKeyEntity(signedPreKey.getId(),