mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-23 11:15:44 +00:00
Remove unused code.
This commit is contained in:
committed by
Cody Henthorne
parent
15aa941436
commit
ea0b8fc594
@@ -70,6 +70,8 @@ wire {
|
||||
protoPath {
|
||||
srcDir("${project.rootDir}/libsignal-service/src/main/protowire")
|
||||
}
|
||||
// Handled by libsignal
|
||||
prune("signalservice.DecryptionErrorMessage")
|
||||
}
|
||||
|
||||
ktlint {
|
||||
|
||||
@@ -176,8 +176,6 @@ class InternalValues internal constructor(store: KeyValueStore) : SignalStoreVal
|
||||
|
||||
var useConversationItemV2Media by booleanValue(CONVERSATION_ITEM_V2_MEDIA, false).defaultForExternalUsers()
|
||||
|
||||
var webSocketShadowingStats by nullableBlobValue(WEB_SOCKET_SHADOWING_STATS, null).defaultForExternalUsers()
|
||||
|
||||
var forceSsre2Capability by booleanValue("internal.force_ssre2_capability", false).defaultForExternalUsers()
|
||||
|
||||
private fun <T> SignalStoreValueDelegate<T>.defaultForExternalUsers(): SignalStoreValueDelegate<T> {
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package org.whispersystems.signalservice.api;
|
||||
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
|
||||
public final class SvrPinData {
|
||||
|
||||
private final MasterKey masterKey;
|
||||
private final TokenResponse tokenResponse;
|
||||
|
||||
// Visible for testing
|
||||
public SvrPinData(MasterKey masterKey, TokenResponse tokenResponse) {
|
||||
this.masterKey = masterKey;
|
||||
this.tokenResponse = tokenResponse;
|
||||
}
|
||||
|
||||
public MasterKey getMasterKey() {
|
||||
return masterKey;
|
||||
}
|
||||
|
||||
public TokenResponse getTokenResponse() {
|
||||
return tokenResponse;
|
||||
}
|
||||
|
||||
public int getRemainingTries() {
|
||||
return tokenResponse.getTries();
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.whispersystems.signalservice.api;
|
||||
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
|
||||
class TokenException extends Exception {
|
||||
|
||||
private final TokenResponse nextToken;
|
||||
private final boolean canAutomaticallyRetry;
|
||||
|
||||
TokenException(TokenResponse nextToken, boolean canAutomaticallyRetry) {
|
||||
this.nextToken = nextToken;
|
||||
this.canAutomaticallyRetry = canAutomaticallyRetry;
|
||||
}
|
||||
|
||||
public TokenResponse getToken() {
|
||||
return nextToken;
|
||||
}
|
||||
|
||||
public boolean isCanAutomaticallyRetry() {
|
||||
return canAutomaticallyRetry;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package org.whispersystems.signalservice.api.groupsv2;
|
||||
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public final class UuidProfileKey {
|
||||
|
||||
private final UUID uuid;
|
||||
private final ProfileKey profileKey;
|
||||
|
||||
public UuidProfileKey(UUID uuid, ProfileKey profileKey) {
|
||||
this.uuid = uuid;
|
||||
this.profileKey = profileKey;
|
||||
}
|
||||
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public ProfileKey getProfileKey() {
|
||||
return profileKey;
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.messages;
|
||||
|
||||
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Group information to include in SignalServiceMessages destined to groups.
|
||||
*
|
||||
* This class represents a "context" that is included with Signal Service messages
|
||||
* to make them group messages. There are three types of context:
|
||||
*
|
||||
* 1) Update -- Sent when either creating a group, or updating the properties
|
||||
* of a group (such as the avatar icon, membership list, or title).
|
||||
* 2) Deliver -- Sent when a message is to be delivered to an existing group.
|
||||
* 3) Quit -- Sent when the sender wishes to leave an existing group.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class SignalServiceGroup {
|
||||
|
||||
public enum Type {
|
||||
UNKNOWN,
|
||||
UPDATE,
|
||||
DELIVER,
|
||||
QUIT,
|
||||
REQUEST_INFO
|
||||
}
|
||||
|
||||
private final byte[] groupId;
|
||||
private final Type type;
|
||||
private final Optional<String> name;
|
||||
private final Optional<List<SignalServiceAddress>> members;
|
||||
private final Optional<SignalServiceAttachment> avatar;
|
||||
|
||||
|
||||
/**
|
||||
* Construct a DELIVER group context.
|
||||
* @param groupId
|
||||
*/
|
||||
public SignalServiceGroup(byte[] groupId) {
|
||||
this(Type.DELIVER, groupId, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a group context.
|
||||
* @param type The group message type (update, deliver, quit).
|
||||
* @param groupId The group ID.
|
||||
* @param name The group title.
|
||||
* @param members The group membership list.
|
||||
* @param avatar The group avatar icon.
|
||||
*/
|
||||
public SignalServiceGroup(Type type, byte[] groupId, String name,
|
||||
List<SignalServiceAddress> members,
|
||||
SignalServiceAttachment avatar)
|
||||
{
|
||||
this.type = type;
|
||||
this.groupId = groupId;
|
||||
this.name = Optional.ofNullable(name);
|
||||
this.members = Optional.ofNullable(members);
|
||||
this.avatar = Optional.ofNullable(avatar);
|
||||
}
|
||||
|
||||
public byte[] getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Optional<String> getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Optional<List<SignalServiceAddress>> getMembers() {
|
||||
return members;
|
||||
}
|
||||
|
||||
public Optional<SignalServiceAttachment> getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public static Builder newUpdateBuilder() {
|
||||
return new Builder(Type.UPDATE);
|
||||
}
|
||||
|
||||
public static Builder newBuilder(Type type) {
|
||||
return new Builder(type);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private Type type;
|
||||
private byte[] id;
|
||||
private String name;
|
||||
private List<SignalServiceAddress> members;
|
||||
private SignalServiceAttachment avatar;
|
||||
|
||||
private Builder(Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Builder withId(byte[] id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMembers(List<SignalServiceAddress> members) {
|
||||
this.members = members;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withAvatar(SignalServiceAttachment avatar) {
|
||||
this.avatar = avatar;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SignalServiceGroup build() {
|
||||
if (id == null) throw new IllegalArgumentException("No group ID specified!");
|
||||
|
||||
if (type == Type.UPDATE && name == null && members == null && avatar == null) {
|
||||
throw new IllegalArgumentException("Group update with no updates!");
|
||||
}
|
||||
|
||||
return new SignalServiceGroup(type, id, name, members, avatar);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package org.whispersystems.signalservice.api.messages;
|
||||
|
||||
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
|
||||
|
||||
/**
|
||||
* When someone sends a message to your PNI, you need to attach one of these PNI signature messages,
|
||||
* proving that you own the PNI identity.
|
||||
*
|
||||
* The signature is generated by signing your ACI public key with your PNI identity.
|
||||
*/
|
||||
public class SignalServicePniSignatureMessage {
|
||||
|
||||
private final PNI pni;
|
||||
private final byte[] signature;
|
||||
|
||||
public SignalServicePniSignatureMessage(PNI pni, byte[] signature) {
|
||||
this.pni = pni;
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public PNI getPni() {
|
||||
return pni;
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
return signature;
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.messages.multidevice;
|
||||
|
||||
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class DeviceGroup {
|
||||
|
||||
private final byte[] id;
|
||||
private final Optional<String> name;
|
||||
private final List<SignalServiceAddress> members;
|
||||
private final Optional<SignalServiceAttachmentStream> avatar;
|
||||
private final boolean active;
|
||||
private final Optional<Integer> expirationTimer;
|
||||
private final Optional<String> color;
|
||||
private final boolean blocked;
|
||||
private final Optional<Integer> inboxPosition;
|
||||
private final boolean archived;
|
||||
|
||||
public DeviceGroup(byte[] id,
|
||||
Optional<String> name,
|
||||
List<SignalServiceAddress> members,
|
||||
Optional<SignalServiceAttachmentStream> avatar,
|
||||
boolean active,
|
||||
Optional<Integer> expirationTimer,
|
||||
Optional<String> color,
|
||||
boolean blocked,
|
||||
Optional<Integer> inboxPosition,
|
||||
boolean archived)
|
||||
{
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.members = members;
|
||||
this.avatar = avatar;
|
||||
this.active = active;
|
||||
this.expirationTimer = expirationTimer;
|
||||
this.color = color;
|
||||
this.blocked = blocked;
|
||||
this.inboxPosition = inboxPosition;
|
||||
this.archived = archived;
|
||||
}
|
||||
|
||||
public Optional<SignalServiceAttachmentStream> getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public Optional<String> getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public byte[] getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public List<SignalServiceAddress> getMembers() {
|
||||
return members;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public Optional<Integer> getExpirationTimer() {
|
||||
return expirationTimer;
|
||||
}
|
||||
|
||||
public Optional<String> getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public boolean isBlocked() {
|
||||
return blocked;
|
||||
}
|
||||
|
||||
public Optional<Integer> getInboxPosition() {
|
||||
return inboxPosition;
|
||||
}
|
||||
|
||||
public boolean isArchived() {
|
||||
return archived;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.svr
|
||||
|
||||
import org.signal.libsignal.svr2.PinHash
|
||||
import org.whispersystems.signalservice.internal.push.AuthCredentials
|
||||
|
||||
/**
|
||||
* Encapsulates the various dependencies needed to create a [PinHash] in SVR2 without having to expose them.
|
||||
*/
|
||||
internal class Svr2PinHasher(
|
||||
private val authCredentials: AuthCredentials,
|
||||
private val mrEnclave: ByteArray
|
||||
) {
|
||||
fun hash(normalizedPin: ByteArray): PinHash {
|
||||
return PinHash.svr2(normalizedPin, authCredentials.username(), mrEnclave)
|
||||
}
|
||||
}
|
||||
@@ -193,11 +193,6 @@ internal class Svr2Socket(
|
||||
FAILED
|
||||
}
|
||||
|
||||
data class Response(
|
||||
val response: Svr2Response,
|
||||
val pinHasher: Svr2PinHasher
|
||||
)
|
||||
|
||||
companion object {
|
||||
private val TAG = Svr2Socket::class.java.simpleName
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
package org.whispersystems.signalservice.internal.contacts.crypto;
|
||||
|
||||
import org.signal.libsignal.protocol.util.ByteUtil;
|
||||
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
final class AESCipher {
|
||||
|
||||
private static final int TAG_LENGTH_BYTES = 16;
|
||||
private static final int TAG_LENGTH_BITS = TAG_LENGTH_BYTES * 8;
|
||||
|
||||
static byte[] decrypt(byte[] key, byte[] iv, byte[] ciphertext, byte[] tag) throws InvalidCiphertextException {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(TAG_LENGTH_BITS, iv));
|
||||
|
||||
return cipher.doFinal(ByteUtil.combine(ciphertext, tag));
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException | IllegalBlockSizeException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidKeyException | BadPaddingException e) {
|
||||
throw new InvalidCiphertextException(e);
|
||||
}
|
||||
}
|
||||
|
||||
static AESEncryptedResult encrypt(byte[] key, byte[] aad, byte[] requestData) {
|
||||
try {
|
||||
byte[] iv = Util.getSecretBytes(12);
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
|
||||
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(TAG_LENGTH_BITS, iv));
|
||||
if (aad != null) {
|
||||
cipher.updateAAD(aad);
|
||||
}
|
||||
|
||||
byte[] cipherText = cipher.doFinal(requestData);
|
||||
byte[][] parts = ByteUtil.split(cipherText, cipherText.length - TAG_LENGTH_BYTES, TAG_LENGTH_BYTES);
|
||||
|
||||
byte[] mac = parts[1];
|
||||
byte[] data = parts[0];
|
||||
|
||||
return new AESEncryptedResult(iv, data, mac, aad);
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
static class AESEncryptedResult {
|
||||
final byte[] iv;
|
||||
final byte[] data;
|
||||
final byte[] mac;
|
||||
final byte[] aad;
|
||||
|
||||
private AESEncryptedResult(byte[] iv, byte[] data, byte[] mac, byte[] aad) {
|
||||
this.iv = iv;
|
||||
this.data = data;
|
||||
this.mac = mac;
|
||||
this.aad = aad;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package org.whispersystems.signalservice.internal.contacts.crypto;
|
||||
|
||||
import org.signal.libsignal.protocol.util.ByteUtil;
|
||||
import org.signal.core.util.CryptoUtil;
|
||||
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.AESCipher.AESEncryptedResult;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.DiscoveryRequest;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.DiscoveryResponse;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.QueryEnvelope;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class ContactDiscoveryCipher {
|
||||
|
||||
private ContactDiscoveryCipher() {
|
||||
}
|
||||
|
||||
public static DiscoveryRequest createDiscoveryRequest(List<String> addressBook, Map<String, RemoteAttestation> remoteAttestations) {
|
||||
byte[] queryDataKey = Util.getSecretBytes(32);
|
||||
byte[] queryData = buildQueryData(addressBook);
|
||||
AESEncryptedResult encryptedQueryData = AESCipher.encrypt(queryDataKey, null, queryData);
|
||||
byte[] commitment = CryptoUtil.sha256(queryData);
|
||||
Map<String, QueryEnvelope> envelopes = new HashMap<>(remoteAttestations.size());
|
||||
|
||||
for (Map.Entry<String, RemoteAttestation> entry : remoteAttestations.entrySet()) {
|
||||
envelopes.put(entry.getKey(),
|
||||
buildQueryEnvelope(entry.getValue().getRequestId(),
|
||||
entry.getValue().getKeys().getClientKey(),
|
||||
queryDataKey));
|
||||
}
|
||||
|
||||
return new DiscoveryRequest(addressBook.size(),
|
||||
commitment,
|
||||
encryptedQueryData.iv,
|
||||
encryptedQueryData.data,
|
||||
encryptedQueryData.mac,
|
||||
envelopes);
|
||||
}
|
||||
|
||||
public static byte[] getDiscoveryResponseData(DiscoveryResponse response, Collection<RemoteAttestation> attestations) throws InvalidCiphertextException, IOException {
|
||||
for (RemoteAttestation attestation : attestations) {
|
||||
if (Arrays.equals(response.getRequestId(), attestation.getRequestId())) {
|
||||
return AESCipher.decrypt(attestation.getKeys().getServerKey(), response.getIv(), response.getData(), response.getMac());
|
||||
}
|
||||
}
|
||||
throw new NoMatchingRequestIdException();
|
||||
}
|
||||
|
||||
private static byte[] buildQueryData(List<String> addresses) {
|
||||
try {
|
||||
byte[] nonce = Util.getSecretBytes(32);
|
||||
ByteArrayOutputStream requestDataStream = new ByteArrayOutputStream();
|
||||
|
||||
requestDataStream.write(nonce);
|
||||
|
||||
for (String address : addresses) {
|
||||
requestDataStream.write(ByteUtil.longToByteArray(Long.parseLong(address)));
|
||||
}
|
||||
|
||||
return requestDataStream.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static QueryEnvelope buildQueryEnvelope(byte[] requestId, byte[] clientKey, byte[] queryDataKey) {
|
||||
AESEncryptedResult result = AESCipher.encrypt(clientKey, requestId, queryDataKey);
|
||||
return new QueryEnvelope(requestId, result.iv, result.data, result.mac);
|
||||
}
|
||||
|
||||
static class NoMatchingRequestIdException extends IOException {
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
package org.whispersystems.signalservice.internal.contacts.crypto;
|
||||
|
||||
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.KeyBackupRequest;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.KeyBackupResponse;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
import org.whispersystems.signalservice.internal.keybackup.protos.BackupRequest;
|
||||
import org.whispersystems.signalservice.internal.keybackup.protos.BackupResponse;
|
||||
import org.whispersystems.signalservice.internal.keybackup.protos.DeleteRequest;
|
||||
import org.whispersystems.signalservice.internal.keybackup.protos.DeleteResponse;
|
||||
import org.whispersystems.signalservice.internal.keybackup.protos.Request;
|
||||
import org.whispersystems.signalservice.internal.keybackup.protos.Response;
|
||||
import org.whispersystems.signalservice.internal.keybackup.protos.RestoreRequest;
|
||||
import org.whispersystems.signalservice.internal.keybackup.protos.RestoreResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
public final class KeyBackupCipher {
|
||||
|
||||
private KeyBackupCipher() {
|
||||
}
|
||||
|
||||
private static final long VALID_FROM_BUFFER_MS = TimeUnit.DAYS.toMillis(1);
|
||||
|
||||
public static KeyBackupRequest createKeyBackupRequest(byte[] kbsAccessKey,
|
||||
byte[] kbsData,
|
||||
TokenResponse token,
|
||||
RemoteAttestation remoteAttestation,
|
||||
byte[] serviceId,
|
||||
int tries)
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
BackupRequest backupRequest = new BackupRequest.Builder()
|
||||
.serviceId(ByteString.of(serviceId))
|
||||
.backupId(ByteString.of(token.getBackupId()))
|
||||
.token(ByteString.of(token.getToken()))
|
||||
.validFrom(getValidFromSeconds(now))
|
||||
.data_(ByteString.of(kbsData))
|
||||
.pin(ByteString.of(kbsAccessKey))
|
||||
.tries(tries)
|
||||
.build();
|
||||
|
||||
Request requestData = new Request.Builder().backup(backupRequest).build();
|
||||
|
||||
return createKeyBackupRequest(requestData, remoteAttestation, "backup");
|
||||
}
|
||||
|
||||
public static KeyBackupRequest createKeyRestoreRequest(byte[] kbsAccessKey,
|
||||
TokenResponse token,
|
||||
RemoteAttestation remoteAttestation,
|
||||
byte[] serviceId)
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
RestoreRequest restoreRequest = new RestoreRequest.Builder()
|
||||
.serviceId(ByteString.of(serviceId))
|
||||
.backupId(ByteString.of(token.getBackupId()))
|
||||
.token(ByteString.of(token.getToken()))
|
||||
.validFrom(getValidFromSeconds(now))
|
||||
.pin(ByteString.of(kbsAccessKey))
|
||||
.build();
|
||||
|
||||
Request request = new Request.Builder().restore(restoreRequest).build();
|
||||
|
||||
return createKeyBackupRequest(request, remoteAttestation, "restore");
|
||||
}
|
||||
|
||||
public static KeyBackupRequest createKeyDeleteRequest(TokenResponse token,
|
||||
RemoteAttestation remoteAttestation,
|
||||
byte[] serviceId)
|
||||
{
|
||||
DeleteRequest deleteRequest = new DeleteRequest.Builder()
|
||||
.serviceId(ByteString.of(serviceId))
|
||||
.backupId(ByteString.of(token.getBackupId()))
|
||||
.build();
|
||||
|
||||
Request request = new Request.Builder().delete(deleteRequest).build();
|
||||
|
||||
return createKeyBackupRequest(request, remoteAttestation, "delete");
|
||||
}
|
||||
|
||||
public static BackupResponse getKeyBackupResponse(KeyBackupResponse response, RemoteAttestation remoteAttestation)
|
||||
throws InvalidCiphertextException, IOException
|
||||
{
|
||||
byte[] data = decryptData(response, remoteAttestation);
|
||||
|
||||
Response backupResponse = Response.ADAPTER.decode(data);
|
||||
|
||||
return backupResponse.backup;
|
||||
}
|
||||
|
||||
public static RestoreResponse getKeyRestoreResponse(KeyBackupResponse response, RemoteAttestation remoteAttestation)
|
||||
throws InvalidCiphertextException, IOException
|
||||
{
|
||||
byte[] data = decryptData(response, remoteAttestation);
|
||||
|
||||
return Response.ADAPTER.decode(data).restore;
|
||||
}
|
||||
|
||||
public static DeleteResponse getKeyDeleteResponseStatus(KeyBackupResponse response, RemoteAttestation remoteAttestation)
|
||||
throws InvalidCiphertextException, IOException
|
||||
{
|
||||
byte[] data = decryptData(response, remoteAttestation);
|
||||
|
||||
return DeleteResponse.ADAPTER.decode(data);
|
||||
}
|
||||
|
||||
private static KeyBackupRequest createKeyBackupRequest(Request requestData, RemoteAttestation remoteAttestation, String type) {
|
||||
byte[] clientKey = remoteAttestation.getKeys().getClientKey();
|
||||
byte[] aad = remoteAttestation.getRequestId();
|
||||
|
||||
AESCipher.AESEncryptedResult aesEncryptedResult = AESCipher.encrypt(clientKey, aad, requestData.encode());
|
||||
|
||||
return new KeyBackupRequest(aesEncryptedResult.aad, aesEncryptedResult.iv, aesEncryptedResult.data, aesEncryptedResult.mac, type);
|
||||
}
|
||||
|
||||
private static byte[] decryptData(KeyBackupResponse response, RemoteAttestation remoteAttestation) throws InvalidCiphertextException {
|
||||
return AESCipher.decrypt(remoteAttestation.getKeys().getServerKey(), response.getIv(), response.getData(), response.getMac());
|
||||
}
|
||||
|
||||
private static long getValidFromSeconds(long nowMs) {
|
||||
return TimeUnit.MILLISECONDS.toSeconds(nowMs - VALID_FROM_BUFFER_MS);
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package org.whispersystems.signalservice.internal.contacts.crypto;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
public class Quote {
|
||||
|
||||
private static final long SGX_FLAGS_INITTED = 0x0000_0000_0000_0001L;
|
||||
private static final long SGX_FLAGS_DEBUG = 0x0000_0000_0000_0002L;
|
||||
private static final long SGX_FLAGS_MODE64BIT = 0x0000_0000_0000_0004L;
|
||||
private static final long SGX_FLAGS_PROVISION_KEY = 0x0000_0000_0000_0004L;
|
||||
private static final long SGX_FLAGS_EINITTOKEN_KEY = 0x0000_0000_0000_0004L;
|
||||
private static final long SGX_FLAGS_RESERVED = 0xFFFF_FFFF_FFFF_FFC8L;
|
||||
private static final long SGX_XFRM_LEGACY = 0x0000_0000_0000_0003L;
|
||||
private static final long SGX_XFRM_AVX = 0x0000_0000_0000_0006L;
|
||||
private static final long SGX_XFRM_RESERVED = 0xFFFF_FFFF_FFFF_FFF8L;
|
||||
|
||||
private final int version;
|
||||
private final boolean isSigLinkable;
|
||||
private final long gid;
|
||||
private final int qeSvn;
|
||||
private final int pceSvn;
|
||||
private final byte[] basename = new byte[32];
|
||||
private final byte[] cpuSvn = new byte[16];
|
||||
private final long flags;
|
||||
private final long xfrm;
|
||||
private final byte[] mrenclave = new byte[32];
|
||||
private final byte[] mrsigner = new byte[32];
|
||||
private final int isvProdId;
|
||||
private final int isvSvn;
|
||||
private final byte[] reportData = new byte[64];
|
||||
private final byte[] signature;
|
||||
private final byte[] quoteBytes;
|
||||
|
||||
public Quote(byte[] quoteBytes) throws InvalidQuoteFormatException {
|
||||
this.quoteBytes = quoteBytes;
|
||||
|
||||
ByteBuffer quoteBuf = ByteBuffer.wrap(quoteBytes);
|
||||
quoteBuf.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
this.version = quoteBuf.getShort(0) & 0xFFFF;
|
||||
if (!(version >= 1 && version <= 2)) {
|
||||
throw new InvalidQuoteFormatException("unknown_quote_version "+version);
|
||||
}
|
||||
|
||||
int sign_type = quoteBuf.getShort(2) & 0xFFFF;
|
||||
if ((sign_type & ~1) != 0) {
|
||||
throw new InvalidQuoteFormatException("unknown_quote_sign_type "+sign_type);
|
||||
}
|
||||
|
||||
this.isSigLinkable = sign_type == 1;
|
||||
this.gid = quoteBuf.getInt(4) & 0xFFFF_FFFF;
|
||||
this.qeSvn = quoteBuf.getShort(8) & 0xFFFF;
|
||||
|
||||
if (version > 1) {
|
||||
this.pceSvn = quoteBuf.getShort(10) & 0xFFFF;
|
||||
} else {
|
||||
readZero(quoteBuf, 10, 2);
|
||||
this.pceSvn = 0;
|
||||
}
|
||||
|
||||
readZero(quoteBuf, 12, 4); // xeid (reserved)
|
||||
read(quoteBuf, 16, basename);
|
||||
|
||||
//
|
||||
// report_body
|
||||
//
|
||||
|
||||
read(quoteBuf, 48, cpuSvn);
|
||||
readZero(quoteBuf, 64, 4); // misc_select (reserved)
|
||||
readZero(quoteBuf, 68, 28); // reserved1
|
||||
this.flags = quoteBuf.getLong(96);
|
||||
if ((flags & SGX_FLAGS_RESERVED ) != 0 ||
|
||||
(flags & SGX_FLAGS_INITTED ) == 0 ||
|
||||
(flags & SGX_FLAGS_MODE64BIT) == 0) {
|
||||
throw new InvalidQuoteFormatException("bad_quote_flags "+flags);
|
||||
}
|
||||
this.xfrm = quoteBuf.getLong(104);
|
||||
if ((xfrm & SGX_XFRM_RESERVED) != 0) {
|
||||
throw new InvalidQuoteFormatException("bad_quote_xfrm "+xfrm);
|
||||
}
|
||||
read(quoteBuf, 112, mrenclave);
|
||||
readZero(quoteBuf, 144, 32); // reserved2
|
||||
read(quoteBuf, 176, mrsigner);
|
||||
readZero(quoteBuf, 208, 96); // reserved3
|
||||
this.isvProdId = quoteBuf.getShort(304) & 0xFFFF;
|
||||
this.isvSvn = quoteBuf.getShort(306) & 0xFFFF;
|
||||
readZero(quoteBuf, 308, 60); // reserved4
|
||||
read(quoteBuf, 368, reportData);
|
||||
|
||||
// quote signature
|
||||
int sig_len = quoteBuf.getInt(432) & 0xFFFF_FFFF;
|
||||
if (sig_len != quoteBytes.length - 436) {
|
||||
throw new InvalidQuoteFormatException("bad_quote_sig_len "+sig_len);
|
||||
}
|
||||
this.signature = new byte[sig_len];
|
||||
read(quoteBuf, 436, signature);
|
||||
}
|
||||
|
||||
public byte[] getReportData() {
|
||||
return reportData;
|
||||
}
|
||||
|
||||
private void read(ByteBuffer quoteBuf, int pos, byte[] buf) {
|
||||
quoteBuf.position(pos);
|
||||
quoteBuf.get(buf);
|
||||
}
|
||||
|
||||
private void readZero(ByteBuffer quoteBuf, int pos, int count) {
|
||||
byte[] zeroBuf = new byte[count];
|
||||
read(quoteBuf, pos, zeroBuf);
|
||||
for (int zeroBufIdx = 0; zeroBufIdx < count; zeroBufIdx++) {
|
||||
if (zeroBuf[zeroBufIdx] != 0) {
|
||||
throw new IllegalArgumentException("quote_reserved_mismatch "+pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getQuoteBytes() {
|
||||
return quoteBytes;
|
||||
}
|
||||
|
||||
public byte[] getMrenclave() {
|
||||
return mrenclave;
|
||||
}
|
||||
|
||||
public boolean isDebugQuote() {
|
||||
return (flags & SGX_FLAGS_DEBUG) != 0;
|
||||
}
|
||||
|
||||
public static class InvalidQuoteFormatException extends Exception {
|
||||
public InvalidQuoteFormatException(String value) {
|
||||
super(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package org.whispersystems.signalservice.internal.contacts.crypto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class RemoteAttestation {
|
||||
|
||||
private final byte[] requestId;
|
||||
private final RemoteAttestationKeys keys;
|
||||
private final List<String> cookies;
|
||||
|
||||
public RemoteAttestation(byte[] requestId, RemoteAttestationKeys keys, List<String> cookies) {
|
||||
this.requestId = requestId;
|
||||
this.keys = keys;
|
||||
this.cookies = cookies;
|
||||
}
|
||||
|
||||
public byte[] getRequestId() {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
public RemoteAttestationKeys getKeys() {
|
||||
return keys;
|
||||
}
|
||||
|
||||
public List<String> getCookies() {
|
||||
return cookies;
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
package org.whispersystems.signalservice.internal.contacts.crypto;
|
||||
|
||||
import org.signal.libsignal.protocol.util.ByteUtil;
|
||||
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.RemoteAttestationResponse;
|
||||
import org.whispersystems.signalservice.internal.util.Hex;
|
||||
import org.whispersystems.signalservice.internal.util.JsonUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.CertPathValidatorException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Period;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public final class RemoteAttestationCipher {
|
||||
|
||||
private static final Set<String> ALLOWED_ADVISORIES = new HashSet<String>() {{
|
||||
add("INTEL-SA-00334");
|
||||
add("INTEL-SA-00615");
|
||||
}};
|
||||
|
||||
private RemoteAttestationCipher() {
|
||||
}
|
||||
|
||||
private static final Set<Long> SIGNATURE_BODY_VERSIONS = new HashSet<Long>() {{
|
||||
add(3L);
|
||||
add(4L);
|
||||
}};
|
||||
|
||||
public static byte[] getRequestId(RemoteAttestationKeys keys, RemoteAttestationResponse response) throws InvalidCiphertextException {
|
||||
return AESCipher.decrypt(keys.getServerKey(), response.getIv(), response.getCiphertext(), response.getTag());
|
||||
}
|
||||
|
||||
public static void verifyServerQuote(Quote quote, byte[] serverPublicStatic, String mrenclave)
|
||||
throws UnauthenticatedQuoteException
|
||||
{
|
||||
try {
|
||||
byte[] theirServerPublicStatic = new byte[serverPublicStatic.length];
|
||||
System.arraycopy(quote.getReportData(), 0, theirServerPublicStatic, 0, theirServerPublicStatic.length);
|
||||
|
||||
if (!MessageDigest.isEqual(theirServerPublicStatic, serverPublicStatic)) {
|
||||
throw new UnauthenticatedQuoteException("Response quote has unauthenticated report data!");
|
||||
}
|
||||
|
||||
if (!MessageDigest.isEqual(Hex.fromStringCondensed(mrenclave), quote.getMrenclave())) {
|
||||
throw new UnauthenticatedQuoteException("The response quote has the wrong mrenclave value in it: " + Hex.toStringCondensed(quote.getMrenclave()));
|
||||
}
|
||||
|
||||
if (quote.isDebugQuote()) {
|
||||
throw new UnauthenticatedQuoteException("Received quote for debuggable enclave");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new UnauthenticatedQuoteException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void verifyIasSignature(KeyStore trustStore, String certificates, String signatureBody, String signature, Quote quote)
|
||||
throws SignatureException
|
||||
{
|
||||
if (certificates == null || certificates.isEmpty()) {
|
||||
throw new SignatureException("No certificates.");
|
||||
}
|
||||
|
||||
try {
|
||||
SigningCertificate signingCertificate = new SigningCertificate(certificates, trustStore);
|
||||
signingCertificate.verifySignature(signatureBody, signature);
|
||||
|
||||
SignatureBodyEntity signatureBodyEntity = JsonUtil.fromJson(signatureBody, SignatureBodyEntity.class);
|
||||
|
||||
if (!SIGNATURE_BODY_VERSIONS.contains(signatureBodyEntity.getVersion())) {
|
||||
throw new SignatureException("Unexpected signed quote version " + signatureBodyEntity.getVersion());
|
||||
}
|
||||
|
||||
if (!MessageDigest.isEqual(ByteUtil.trim(signatureBodyEntity.getIsvEnclaveQuoteBody(), 432), ByteUtil.trim(quote.getQuoteBytes(), 432))) {
|
||||
throw new SignatureException("Signed quote is not the same as RA quote: " + Hex.toStringCondensed(signatureBodyEntity.getIsvEnclaveQuoteBody()) + " vs " + Hex.toStringCondensed(quote.getQuoteBytes()));
|
||||
}
|
||||
|
||||
if (!hasValidStatus(signatureBodyEntity)) {
|
||||
throw new SignatureException("Quote status is: " + signatureBodyEntity.getIsvEnclaveQuoteStatus() + " and advisories are: " + Arrays.toString(signatureBodyEntity.getAdvisoryIds()));
|
||||
}
|
||||
|
||||
if (Instant.from(ZonedDateTime.of(LocalDateTime.from(DateTimeFormatter.ofPattern("yyy-MM-dd'T'HH:mm:ss.SSSSSS").parse(signatureBodyEntity.getTimestamp())), ZoneId.of("UTC")))
|
||||
.plus(Period.ofDays(1))
|
||||
.isBefore(Instant.now()))
|
||||
{
|
||||
throw new SignatureException("Signature is expired");
|
||||
}
|
||||
|
||||
} catch (CertificateException | CertPathValidatorException | IOException e) {
|
||||
throw new SignatureException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasValidStatus(SignatureBodyEntity entity) {
|
||||
if ("OK".equals(entity.getIsvEnclaveQuoteStatus())) {
|
||||
return true;
|
||||
} else if ("SW_HARDENING_NEEDED".equals(entity.getIsvEnclaveQuoteStatus())) {
|
||||
return Arrays.stream(entity.getAdvisoryIds()).allMatch(ALLOWED_ADVISORIES::contains);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package org.whispersystems.signalservice.internal.contacts.crypto;
|
||||
|
||||
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.protocol.ecc.Curve;
|
||||
import org.signal.libsignal.protocol.ecc.ECKeyPair;
|
||||
import org.signal.libsignal.protocol.ecc.ECPublicKey;
|
||||
import org.signal.libsignal.protocol.kdf.HKDF;
|
||||
import org.signal.libsignal.protocol.util.ByteUtil;
|
||||
|
||||
public class RemoteAttestationKeys {
|
||||
|
||||
private final byte[] clientKey = new byte[32];
|
||||
private final byte[] serverKey = new byte[32];
|
||||
|
||||
public RemoteAttestationKeys(ECKeyPair keyPair, byte[] serverPublicEphemeral, byte[] serverPublicStatic) throws InvalidKeyException {
|
||||
byte[] ephemeralToEphemeral = Curve.calculateAgreement(ECPublicKey.fromPublicKeyBytes(serverPublicEphemeral), keyPair.getPrivateKey());
|
||||
byte[] ephemeralToStatic = Curve.calculateAgreement(ECPublicKey.fromPublicKeyBytes(serverPublicStatic), keyPair.getPrivateKey());
|
||||
|
||||
byte[] masterSecret = ByteUtil.combine(ephemeralToEphemeral, ephemeralToStatic );
|
||||
byte[] publicKeys = ByteUtil.combine(keyPair.getPublicKey().getPublicKeyBytes(), serverPublicEphemeral, serverPublicStatic);
|
||||
|
||||
byte[] keys = HKDF.deriveSecrets(masterSecret, publicKeys, new byte[0], clientKey.length + serverKey.length);
|
||||
|
||||
System.arraycopy(keys, 0, clientKey, 0, clientKey.length);
|
||||
System.arraycopy(keys, clientKey.length, serverKey, 0, serverKey.length);
|
||||
}
|
||||
|
||||
public byte[] getClientKey() {
|
||||
return clientKey;
|
||||
}
|
||||
|
||||
public byte[] getServerKey() {
|
||||
return serverKey;
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package org.whispersystems.signalservice.internal.contacts.crypto;
|
||||
|
||||
import org.signal.core.util.Base64;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.CertPath;
|
||||
import java.security.cert.CertPathValidator;
|
||||
import java.security.cert.CertPathValidatorException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.PKIXParameters;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class SigningCertificate {
|
||||
|
||||
private final CertPath path;
|
||||
|
||||
public SigningCertificate(String certificateChain, KeyStore trustStore)
|
||||
throws CertificateException, CertPathValidatorException
|
||||
{
|
||||
try {
|
||||
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
|
||||
Collection<X509Certificate> certificatesCollection = (Collection<X509Certificate>) certificateFactory.generateCertificates(new ByteArrayInputStream(certificateChain.getBytes()));
|
||||
List<X509Certificate> certificates = new LinkedList<>(certificatesCollection);
|
||||
PKIXParameters pkixParameters = new PKIXParameters(trustStore);
|
||||
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
|
||||
|
||||
if (certificates.isEmpty()) {
|
||||
throw new CertificateException("No certificates available! Badly-formatted cert chain?");
|
||||
}
|
||||
|
||||
this.path = certificateFactory.generateCertPath(certificates);
|
||||
|
||||
pkixParameters.setRevocationEnabled(false);
|
||||
validator.validate(path, pkixParameters);
|
||||
verifyDistinguishedName(path);
|
||||
} catch (KeyStoreException | InvalidAlgorithmParameterException | NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void verifySignature(String body, String encodedSignature)
|
||||
throws SignatureException
|
||||
{
|
||||
try {
|
||||
Signature signature = Signature.getInstance("SHA256withRSA");
|
||||
signature.initVerify(path.getCertificates().get(0));
|
||||
signature.update(body.getBytes());
|
||||
if (!signature.verify(Base64.decode(encodedSignature.getBytes()))) {
|
||||
throw new SignatureException("Signature verification failed.");
|
||||
}
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyDistinguishedName(CertPath path) throws CertificateException {
|
||||
X509Certificate leaf = (X509Certificate) path.getCertificates().get(0);
|
||||
String distinguishedName = leaf.getSubjectX500Principal().getName();
|
||||
|
||||
if (!"CN=Intel SGX Attestation Report Signing,O=Intel Corporation,L=Santa Clara,ST=CA,C=US".equals(distinguishedName)) {
|
||||
throw new CertificateException("Bad DN: " + distinguishedName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package org.whispersystems.signalservice.internal.contacts.crypto;
|
||||
|
||||
|
||||
public class UnauthenticatedQuoteException extends Exception {
|
||||
public UnauthenticatedQuoteException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
public UnauthenticatedQuoteException(Exception nested) {
|
||||
super(nested);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package org.whispersystems.signalservice.internal.contacts.crypto;
|
||||
|
||||
|
||||
public class UnauthenticatedResponseException extends Exception {
|
||||
public UnauthenticatedResponseException(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
public UnauthenticatedResponseException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +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 java.util.List;
|
||||
|
||||
public class ContactTokenList {
|
||||
|
||||
private List<String> contacts;
|
||||
|
||||
public ContactTokenList(List<String> contacts) {
|
||||
this.contacts = contacts;
|
||||
}
|
||||
|
||||
public ContactTokenList() {}
|
||||
|
||||
public List<String> getContacts() {
|
||||
return contacts;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package org.whispersystems.signalservice.internal.push;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SignalServiceEnvelopeEntityList {
|
||||
|
||||
private List<SignalServiceEnvelopeEntity> messages;
|
||||
|
||||
public SignalServiceEnvelopeEntityList() {}
|
||||
|
||||
public List<SignalServiceEnvelopeEntity> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.whispersystems.signalservice.internal.push;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public final class SignalServiceMessagesResult {
|
||||
private final List<SignalServiceEnvelopeEntity> envelopes;
|
||||
private final long serverDeliveredTimestamp;
|
||||
|
||||
SignalServiceMessagesResult(List<SignalServiceEnvelopeEntity> envelopes, long serverDeliveredTimestamp) {
|
||||
this.envelopes = envelopes;
|
||||
this.serverDeliveredTimestamp = serverDeliveredTimestamp;
|
||||
}
|
||||
|
||||
public List<SignalServiceEnvelopeEntity> getEnvelopes() {
|
||||
return envelopes;
|
||||
}
|
||||
|
||||
public long getServerDeliveredTimestamp() {
|
||||
return serverDeliveredTimestamp;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package org.whispersystems.signalservice.internal.push;
|
||||
|
||||
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Exception that indicates that the data message contains something that is not supported by this
|
||||
* version of the application. Subclasses provide more specific information about what data was
|
||||
* found that is not supported.
|
||||
*/
|
||||
public abstract class UnsupportedDataMessageException extends Exception {
|
||||
|
||||
private final String sender;
|
||||
private final int senderDevice;
|
||||
private final Optional<SignalServiceGroupV2> group;
|
||||
|
||||
protected UnsupportedDataMessageException(String message,
|
||||
String sender,
|
||||
int senderDevice,
|
||||
Optional<SignalServiceGroupV2> group)
|
||||
{
|
||||
super(message);
|
||||
this.sender = sender;
|
||||
this.senderDevice = senderDevice;
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public String getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public int getSenderDevice() {
|
||||
return senderDevice;
|
||||
}
|
||||
|
||||
public Optional<SignalServiceGroupV2> getGroup() {
|
||||
return group;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.whispersystems.signalservice.internal.push;
|
||||
|
||||
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Exception that indicates that the data message has a higher required protocol version than the
|
||||
* current client is capable of interpreting.
|
||||
*/
|
||||
public final class UnsupportedDataMessageProtocolVersionException extends UnsupportedDataMessageException {
|
||||
private final int requiredVersion;
|
||||
|
||||
public UnsupportedDataMessageProtocolVersionException(int currentVersion,
|
||||
int requiredVersion,
|
||||
String sender,
|
||||
int senderDevice,
|
||||
Optional<SignalServiceGroupV2> group) {
|
||||
super("Required version: " + requiredVersion + ", Our version: " + currentVersion, sender, senderDevice, group);
|
||||
this.requiredVersion = requiredVersion;
|
||||
}
|
||||
|
||||
public int getRequiredVersion() {
|
||||
return requiredVersion;
|
||||
}
|
||||
}
|
||||
@@ -21,23 +21,6 @@ message SignalServiceContentProto {
|
||||
}
|
||||
}
|
||||
|
||||
message SignalServiceEnvelopeProto {
|
||||
optional int32 type = 1;
|
||||
optional string sourceServiceId = 2;
|
||||
reserved /*sourceE164*/ 3;
|
||||
optional int32 deviceId = 4;
|
||||
reserved /*legacyMessage*/ 5;
|
||||
optional bytes content = 6;
|
||||
optional int64 timestamp = 7;
|
||||
optional int64 serverReceivedTimestamp = 8;
|
||||
optional int64 serverDeliveredTimestamp = 9;
|
||||
optional string serverGuid = 10;
|
||||
optional string destinationServiceId = 11;
|
||||
optional bool urgent = 12 [default = true];
|
||||
optional bool story = 13;
|
||||
optional bytes reportingToken = 14;
|
||||
}
|
||||
|
||||
message MetadataProto {
|
||||
optional AddressProto address = 1;
|
||||
optional int32 senderDevice = 2;
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2019 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
syntax = "proto2";
|
||||
|
||||
package textsecure;
|
||||
|
||||
option java_package = "org.whispersystems.signalservice.internal.keybackup.protos";
|
||||
option java_multiple_files = true;
|
||||
|
||||
message Request {
|
||||
optional BackupRequest backup = 1;
|
||||
optional RestoreRequest restore = 2;
|
||||
optional DeleteRequest delete = 3;
|
||||
}
|
||||
|
||||
message Response {
|
||||
optional BackupResponse backup = 1;
|
||||
optional RestoreResponse restore = 2;
|
||||
optional DeleteResponse delete = 3;
|
||||
}
|
||||
|
||||
message BackupRequest {
|
||||
optional bytes serviceId = 1;
|
||||
optional bytes backupId = 2;
|
||||
optional bytes token = 3;
|
||||
optional uint64 validFrom = 4;
|
||||
optional bytes data = 5;
|
||||
optional bytes pin = 6;
|
||||
optional uint32 tries = 7;
|
||||
}
|
||||
|
||||
message BackupResponse {
|
||||
enum Status {
|
||||
OK = 1;
|
||||
ALREADY_EXISTS = 2;
|
||||
NOT_YET_VALID = 3;
|
||||
}
|
||||
|
||||
optional Status status = 1;
|
||||
optional bytes token = 2;
|
||||
}
|
||||
|
||||
message RestoreRequest {
|
||||
optional bytes serviceId = 1;
|
||||
optional bytes backupId = 2;
|
||||
optional bytes token = 3;
|
||||
optional uint64 validFrom = 4;
|
||||
optional bytes pin = 5;
|
||||
}
|
||||
|
||||
message RestoreResponse {
|
||||
enum Status {
|
||||
OK = 1;
|
||||
TOKEN_MISMATCH = 2;
|
||||
NOT_YET_VALID = 3;
|
||||
MISSING = 4;
|
||||
PIN_MISMATCH = 5;
|
||||
}
|
||||
|
||||
optional Status status = 1;
|
||||
optional bytes token = 2;
|
||||
optional bytes data = 3;
|
||||
optional uint32 tries = 4;
|
||||
}
|
||||
|
||||
message DeleteRequest {
|
||||
optional bytes serviceId = 1;
|
||||
optional bytes backupId = 2;
|
||||
}
|
||||
|
||||
message DeleteResponse {
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
syntax = "proto3";
|
||||
|
||||
package signalservice;
|
||||
|
||||
option java_package = "org.whispersystems.signalservice.internal.websocket";
|
||||
option java_outer_classname = "ShadowingStats";
|
||||
|
||||
message Snapshot {
|
||||
int32 requestsCompared = 1;
|
||||
int32 failures = 2;
|
||||
int32 badStatuses = 3;
|
||||
int32 reconnects = 4;
|
||||
int64 lastNotified = 5;
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package org.whispersystems.signalservice.api.crypto;
|
||||
|
||||
import org.conscrypt.Conscrypt;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.SigningCertificate;
|
||||
import org.signal.core.util.Base64;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLDecoder;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Security;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.CertPathValidatorException;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
public class SigningCertificateTest {
|
||||
|
||||
static {
|
||||
// https://github.com/google/conscrypt/issues/1034
|
||||
if (!System.getProperty("os.arch").equals("aarch64")) {
|
||||
Security.insertProviderAt(Conscrypt.newProvider(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGoodSignature() throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException, CertPathValidatorException, SignatureException {
|
||||
String certificateChain = URLDecoder.decode("-----BEGIN%20CERTIFICATE-----%0AMIIEoTCCAwmgAwIBAgIJANEHdl0yo7CWMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV%0ABAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwLU2FudGEgQ2xhcmExGjAYBgNV%0ABAoMEUludGVsIENvcnBvcmF0aW9uMTAwLgYDVQQDDCdJbnRlbCBTR1ggQXR0ZXN0%0AYXRpb24gUmVwb3J0IFNpZ25pbmcgQ0EwHhcNMTYxMTIyMDkzNjU4WhcNMjYxMTIw%0AMDkzNjU4WjB7MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExFDASBgNVBAcMC1Nh%0AbnRhIENsYXJhMRowGAYDVQQKDBFJbnRlbCBDb3Jwb3JhdGlvbjEtMCsGA1UEAwwk%0ASW50ZWwgU0dYIEF0dGVzdGF0aW9uIFJlcG9ydCBTaWduaW5nMIIBIjANBgkqhkiG%0A9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqXot4OZuphR8nudFrAFiaGxxkgma/Es/BA%2Bt%0AbeCTUR106AL1ENcWA4FX3K%2BE9BBL0/7X5rj5nIgX/R/1ubhkKWw9gfqPG3KeAtId%0Acv/uTO1yXv50vqaPvE1CRChvzdS/ZEBqQ5oVvLTPZ3VEicQjlytKgN9cLnxbwtuv%0ALUK7eyRPfJW/ksddOzP8VBBniolYnRCD2jrMRZ8nBM2ZWYwnXnwYeOAHV%2BW9tOhA%0AImwRwKF/95yAsVwd21ryHMJBcGH70qLagZ7Ttyt%2B%2BqO/6%2BKAXJuKwZqjRlEtSEz8%0AgZQeFfVYgcwSfo96oSMAzVr7V0L6HSDLRnpb6xxmbPdqNol4tQIDAQABo4GkMIGh%0AMB8GA1UdIwQYMBaAFHhDe3amfrzQr35CN%2Bs1fDuHAVE8MA4GA1UdDwEB/wQEAwIG%0AwDAMBgNVHRMBAf8EAjAAMGAGA1UdHwRZMFcwVaBToFGGT2h0dHA6Ly90cnVzdGVk%0Ac2VydmljZXMuaW50ZWwuY29tL2NvbnRlbnQvQ1JML1NHWC9BdHRlc3RhdGlvblJl%0AcG9ydFNpZ25pbmdDQS5jcmwwDQYJKoZIhvcNAQELBQADggGBAGcIthtcK9IVRz4r%0ARq%2BZKE%2B7k50/OxUsmW8aavOzKb0iCx07YQ9rzi5nU73tME2yGRLzhSViFs/LpFa9%0AlpQL6JL1aQwmDR74TxYGBAIi5f4I5TJoCCEqRHz91kpG6Uvyn2tLmnIdJbPE4vYv%0AWLrtXXfFBSSPD4Afn7%2B3/XUggAlc7oCTizOfbbtOFlYA4g5KcYgS1J2ZAeMQqbUd%0AZseZCcaZZZn65tdqee8UXZlDvx0%2BNdO0LR%2B5pFy%2BjuM0wWbu59MvzcmTXbjsi7HY%0A6zd53Yq5K244fwFHRQ8eOB0IWB%2B4PfM7FeAApZvlfqlKOlLcZL2uyVmzRkyR5yW7%0A2uo9mehX44CiPJ2fse9Y6eQtcfEhMPkmHXI01sN%2BKwPbpA39%2BxOsStjhP9N1Y1a2%0AtQAVo%2ByVgLgV2Hws73Fc0o3wC78qPEA%2Bv2aRs/Be3ZFDgDyghc/1fgU%2B7C%2BP6kbq%0Ad4poyb6IW8KCJbxfMJvkordNOgOUUxndPHEi/tb/U7uLjLOgPA%3D%3D%0A-----END%20CERTIFICATE-----%0A-----BEGIN%20CERTIFICATE-----%0AMIIFSzCCA7OgAwIBAgIJANEHdl0yo7CUMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV%0ABAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwLU2FudGEgQ2xhcmExGjAYBgNV%0ABAoMEUludGVsIENvcnBvcmF0aW9uMTAwLgYDVQQDDCdJbnRlbCBTR1ggQXR0ZXN0%0AYXRpb24gUmVwb3J0IFNpZ25pbmcgQ0EwIBcNMTYxMTE0MTUzNzMxWhgPMjA0OTEy%0AMzEyMzU5NTlaMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwL%0AU2FudGEgQ2xhcmExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0aW9uMTAwLgYDVQQD%0ADCdJbnRlbCBTR1ggQXR0ZXN0YXRpb24gUmVwb3J0IFNpZ25pbmcgQ0EwggGiMA0G%0ACSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCfPGR%2BtXc8u1EtJzLA10Feu1Wg%2Bp7e%0ALmSRmeaCHbkQ1TF3Nwl3RmpqXkeGzNLd69QUnWovYyVSndEMyYc3sHecGgfinEeh%0ArgBJSEdsSJ9FpaFdesjsxqzGRa20PYdnnfWcCTvFoulpbFR4VBuXnnVLVzkUvlXT%0AL/TAnd8nIZk0zZkFJ7P5LtePvykkar7LcSQO85wtcQe0R1Raf/sQ6wYKaKmFgCGe%0ANpEJUmg4ktal4qgIAxk%2BQHUxQE42sxViN5mqglB0QJdUot/o9a/V/mMeH8KvOAiQ%0AbyinkNndn%2BBgk5sSV5DFgF0DffVqmVMblt5p3jPtImzBIH0QQrXJq39AT8cRwP5H%0AafuVeLHcDsRp6hol4P%2BZFIhu8mmbI1u0hH3W/0C2BuYXB5PC%2B5izFFh/nP0lc2Lf%0A6rELO9LZdnOhpL1ExFOq9H/B8tPQ84T3Sgb4nAifDabNt/zu6MmCGo5U8lwEFtGM%0ARoOaX4AS%2B909x00lYnmtwsDVWv9vBiJCXRsCAwEAAaOByTCBxjBgBgNVHR8EWTBX%0AMFWgU6BRhk9odHRwOi8vdHJ1c3RlZHNlcnZpY2VzLmludGVsLmNvbS9jb250ZW50%0AL0NSTC9TR1gvQXR0ZXN0YXRpb25SZXBvcnRTaWduaW5nQ0EuY3JsMB0GA1UdDgQW%0ABBR4Q3t2pn680K9%2BQjfrNXw7hwFRPDAfBgNVHSMEGDAWgBR4Q3t2pn680K9%2BQjfr%0ANXw7hwFRPDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkq%0AhkiG9w0BAQsFAAOCAYEAeF8tYMXICvQqeXYQITkV2oLJsp6J4JAqJabHWxYJHGir%0AIEqucRiJSSx%2BHjIJEUVaj8E0QjEud6Y5lNmXlcjqRXaCPOqK0eGRz6hi%2BripMtPZ%0AsFNaBwLQVV905SDjAzDzNIDnrcnXyB4gcDFCvwDFKKgLRjOB/WAqgscDUoGq5ZVi%0AzLUzTqiQPmULAQaB9c6Oti6snEFJiCQ67JLyW/E83/frzCmO5Ru6WjU4tmsmy8Ra%0AUd4APK0wZTGtfPXU7w%2BIBdG5Ez0kE1qzxGQaL4gINJ1zMyleDnbuS8UicjJijvqA%0A152Sq049ESDz%2B1rRGc2NVEqh1KaGXmtXvqxXcTB%2BLjy5Bw2ke0v8iGngFBPqCTVB%0A3op5KBG3RjbF6RRSzwzuWfL7QErNC8WEy5yDVARzTA5%2BxmBc388v9Dm21HGfcC8O%0ADD%2BgT9sSpssq0ascmvH49MOgjt1yoysLtdCtJW/9FZpoOypaHx0R%2BmJTLwPXVMrv%0ADaVzWh5aiEx%2BidkSGMnX%0A-----END%20CERTIFICATE-----%0A");
|
||||
String signature = "Kn2Ya2T039qvEWIzIQeSksNyyCQIkcVjciClcp3a6C766dJANXxLLIn6CfyvUZddMtePrTOLpC2e5QTQxB4RwtWmFfr7nxRdFUtA3dH2DAQL5DqqlmPv46ZWSPfiiOXUsu8vNgX3Z4Znt4Q+dIPIquNPY8ZmiAcpKR7n2K3QtabgOnJ2EyngabY3LMQTtriXbZjpl53ynhVhV1rciMdvMaTz4DUYt7gKi+KeNd3CBFSev+eTgYPC3em96J/3bfVR+wC5m3JGbIBCrwAsbO05JkiNIMck3s+p4d/hwiABR75EplxaWmGgIm6VvUKtGhdJ/cNrmF0nxMX6Vi6N2WaLTA==";
|
||||
String signatureBody = "{\"id\":\"287419896494669543891634765983074535548\",\"timestamp\":\"2019-03-11T20:01:21.658293\",\"version\":3,\"isvEnclaveQuoteStatus\":\"OK\",\"isvEnclaveQuoteBody\":\"AgAAADILAAAIAAcAAAAAAPiLWcRSSA3shraxepsGV9qF4zYUPJgE42ZZZXS2G9zaBQUCBP//AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAHAAAAAAAAAM1s/DQpN7I7G907v5chqlYVrJ/1CnXFUn1EHNMnaCbJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADrzm117Qj8NlEllyDkV4Pae4UgsPjgVXtAA5UsG90gVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACHgz6GaO6bkxfPLBYcR5rEf9Itrt81OEanXteSMcd/BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"}";
|
||||
|
||||
KeyStore keyStore = KeyStore.getInstance("JKS");
|
||||
keyStore.load(getClass().getResourceAsStream("/ias.jks"), "whisper".toCharArray());
|
||||
|
||||
SigningCertificate certificate = new SigningCertificate(certificateChain, keyStore);
|
||||
|
||||
certificate.verifySignature(signatureBody, signature);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadSignature() throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException, CertPathValidatorException, SignatureException {
|
||||
String certificateChain = URLDecoder.decode("-----BEGIN%20CERTIFICATE-----%0AMIIEoTCCAwmgAwIBAgIJANEHdl0yo7CWMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV%0ABAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwLU2FudGEgQ2xhcmExGjAYBgNV%0ABAoMEUludGVsIENvcnBvcmF0aW9uMTAwLgYDVQQDDCdJbnRlbCBTR1ggQXR0ZXN0%0AYXRpb24gUmVwb3J0IFNpZ25pbmcgQ0EwHhcNMTYxMTIyMDkzNjU4WhcNMjYxMTIw%0AMDkzNjU4WjB7MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExFDASBgNVBAcMC1Nh%0AbnRhIENsYXJhMRowGAYDVQQKDBFJbnRlbCBDb3Jwb3JhdGlvbjEtMCsGA1UEAwwk%0ASW50ZWwgU0dYIEF0dGVzdGF0aW9uIFJlcG9ydCBTaWduaW5nMIIBIjANBgkqhkiG%0A9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqXot4OZuphR8nudFrAFiaGxxkgma/Es/BA%2Bt%0AbeCTUR106AL1ENcWA4FX3K%2BE9BBL0/7X5rj5nIgX/R/1ubhkKWw9gfqPG3KeAtId%0Acv/uTO1yXv50vqaPvE1CRChvzdS/ZEBqQ5oVvLTPZ3VEicQjlytKgN9cLnxbwtuv%0ALUK7eyRPfJW/ksddOzP8VBBniolYnRCD2jrMRZ8nBM2ZWYwnXnwYeOAHV%2BW9tOhA%0AImwRwKF/95yAsVwd21ryHMJBcGH70qLagZ7Ttyt%2B%2BqO/6%2BKAXJuKwZqjRlEtSEz8%0AgZQeFfVYgcwSfo96oSMAzVr7V0L6HSDLRnpb6xxmbPdqNol4tQIDAQABo4GkMIGh%0AMB8GA1UdIwQYMBaAFHhDe3amfrzQr35CN%2Bs1fDuHAVE8MA4GA1UdDwEB/wQEAwIG%0AwDAMBgNVHRMBAf8EAjAAMGAGA1UdHwRZMFcwVaBToFGGT2h0dHA6Ly90cnVzdGVk%0Ac2VydmljZXMuaW50ZWwuY29tL2NvbnRlbnQvQ1JML1NHWC9BdHRlc3RhdGlvblJl%0AcG9ydFNpZ25pbmdDQS5jcmwwDQYJKoZIhvcNAQELBQADggGBAGcIthtcK9IVRz4r%0ARq%2BZKE%2B7k50/OxUsmW8aavOzKb0iCx07YQ9rzi5nU73tME2yGRLzhSViFs/LpFa9%0AlpQL6JL1aQwmDR74TxYGBAIi5f4I5TJoCCEqRHz91kpG6Uvyn2tLmnIdJbPE4vYv%0AWLrtXXfFBSSPD4Afn7%2B3/XUggAlc7oCTizOfbbtOFlYA4g5KcYgS1J2ZAeMQqbUd%0AZseZCcaZZZn65tdqee8UXZlDvx0%2BNdO0LR%2B5pFy%2BjuM0wWbu59MvzcmTXbjsi7HY%0A6zd53Yq5K244fwFHRQ8eOB0IWB%2B4PfM7FeAApZvlfqlKOlLcZL2uyVmzRkyR5yW7%0A2uo9mehX44CiPJ2fse9Y6eQtcfEhMPkmHXI01sN%2BKwPbpA39%2BxOsStjhP9N1Y1a2%0AtQAVo%2ByVgLgV2Hws73Fc0o3wC78qPEA%2Bv2aRs/Be3ZFDgDyghc/1fgU%2B7C%2BP6kbq%0Ad4poyb6IW8KCJbxfMJvkordNOgOUUxndPHEi/tb/U7uLjLOgPA%3D%3D%0A-----END%20CERTIFICATE-----%0A-----BEGIN%20CERTIFICATE-----%0AMIIFSzCCA7OgAwIBAgIJANEHdl0yo7CUMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV%0ABAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwLU2FudGEgQ2xhcmExGjAYBgNV%0ABAoMEUludGVsIENvcnBvcmF0aW9uMTAwLgYDVQQDDCdJbnRlbCBTR1ggQXR0ZXN0%0AYXRpb24gUmVwb3J0IFNpZ25pbmcgQ0EwIBcNMTYxMTE0MTUzNzMxWhgPMjA0OTEy%0AMzEyMzU5NTlaMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwL%0AU2FudGEgQ2xhcmExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0aW9uMTAwLgYDVQQD%0ADCdJbnRlbCBTR1ggQXR0ZXN0YXRpb24gUmVwb3J0IFNpZ25pbmcgQ0EwggGiMA0G%0ACSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCfPGR%2BtXc8u1EtJzLA10Feu1Wg%2Bp7e%0ALmSRmeaCHbkQ1TF3Nwl3RmpqXkeGzNLd69QUnWovYyVSndEMyYc3sHecGgfinEeh%0ArgBJSEdsSJ9FpaFdesjsxqzGRa20PYdnnfWcCTvFoulpbFR4VBuXnnVLVzkUvlXT%0AL/TAnd8nIZk0zZkFJ7P5LtePvykkar7LcSQO85wtcQe0R1Raf/sQ6wYKaKmFgCGe%0ANpEJUmg4ktal4qgIAxk%2BQHUxQE42sxViN5mqglB0QJdUot/o9a/V/mMeH8KvOAiQ%0AbyinkNndn%2BBgk5sSV5DFgF0DffVqmVMblt5p3jPtImzBIH0QQrXJq39AT8cRwP5H%0AafuVeLHcDsRp6hol4P%2BZFIhu8mmbI1u0hH3W/0C2BuYXB5PC%2B5izFFh/nP0lc2Lf%0A6rELO9LZdnOhpL1ExFOq9H/B8tPQ84T3Sgb4nAifDabNt/zu6MmCGo5U8lwEFtGM%0ARoOaX4AS%2B909x00lYnmtwsDVWv9vBiJCXRsCAwEAAaOByTCBxjBgBgNVHR8EWTBX%0AMFWgU6BRhk9odHRwOi8vdHJ1c3RlZHNlcnZpY2VzLmludGVsLmNvbS9jb250ZW50%0AL0NSTC9TR1gvQXR0ZXN0YXRpb25SZXBvcnRTaWduaW5nQ0EuY3JsMB0GA1UdDgQW%0ABBR4Q3t2pn680K9%2BQjfrNXw7hwFRPDAfBgNVHSMEGDAWgBR4Q3t2pn680K9%2BQjfr%0ANXw7hwFRPDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkq%0AhkiG9w0BAQsFAAOCAYEAeF8tYMXICvQqeXYQITkV2oLJsp6J4JAqJabHWxYJHGir%0AIEqucRiJSSx%2BHjIJEUVaj8E0QjEud6Y5lNmXlcjqRXaCPOqK0eGRz6hi%2BripMtPZ%0AsFNaBwLQVV905SDjAzDzNIDnrcnXyB4gcDFCvwDFKKgLRjOB/WAqgscDUoGq5ZVi%0AzLUzTqiQPmULAQaB9c6Oti6snEFJiCQ67JLyW/E83/frzCmO5Ru6WjU4tmsmy8Ra%0AUd4APK0wZTGtfPXU7w%2BIBdG5Ez0kE1qzxGQaL4gINJ1zMyleDnbuS8UicjJijvqA%0A152Sq049ESDz%2B1rRGc2NVEqh1KaGXmtXvqxXcTB%2BLjy5Bw2ke0v8iGngFBPqCTVB%0A3op5KBG3RjbF6RRSzwzuWfL7QErNC8WEy5yDVARzTA5%2BxmBc388v9Dm21HGfcC8O%0ADD%2BgT9sSpssq0ascmvH49MOgjt1yoysLtdCtJW/9FZpoOypaHx0R%2BmJTLwPXVMrv%0ADaVzWh5aiEx%2BidkSGMnX%0A-----END%20CERTIFICATE-----%0A");
|
||||
String signature = "Kn2Ya2T039qvEWIzIQeSksNyyCQIkcVjciClcp3a6C766dJANXxLLIn6CfyvUZddMtePrTOLpC2e5QTQxB4RwtWmFfr7nxRdFUtA3dH2DAQL5DqqlmPv46ZWSPfiiOXUsu8vNgX3Z4Znt4Q+dIPIquNPY8ZmiAcpKR7n2K3QtabgOnJ2EyngabY3LMQTtriXbZjpl53ynhVhV1rciMdvMaTz4DUYt7gKi+KeNd3CBFSev+eTgYPC3em96J/3bfVR+wC5m3JGbIBCrwAsbO05JkiNIMck3s+p4d/hwiABR75EplxaWmGgIm6VvUKtGhdJ/cNrmF0nxMX6Vi6N2WaLTA==";
|
||||
String signatureBody = "{\"id\":\"287419896494669543891634765983074535548\",\"timestamp\":\"2019-03-11T20:01:21.658293\",\"version\":3,\"isvEnclaveQuoteStatus\":\"OK\",\"isvEnclaveQuoteBody\":\"AgAAADILAAAIAAcAAAAAAPiLWcRSSA3shraxepsGV9qF4zYUPJgE42ZZZXS2G9zaBQUCBP//AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAHAAAAAAAAAM1s/DQpN7I7G907v5chqlYVrJ/1CnXFUn1EHNMnaCbJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADrzm117Qj8NlEllyDkV4Pae4UgsPjgVXtAA5UsG90gVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACHgz6GaO6bkxfPLBYcR5rEf9Itrt81OEanXteSMcd/BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"}";
|
||||
|
||||
KeyStore keyStore = KeyStore.getInstance("JKS");
|
||||
keyStore.load(getClass().getResourceAsStream("/ias.jks"), "whisper".toCharArray());
|
||||
|
||||
SigningCertificate certificate = new SigningCertificate(certificateChain, keyStore);
|
||||
byte[] decodedSignature = Base64.decode(signature);
|
||||
|
||||
for (int i=0;i<signature.length();i++) {
|
||||
for (int j=0;i<8;i++) {
|
||||
byte[] malformedSignature = new byte[decodedSignature.length];
|
||||
System.arraycopy(decodedSignature, 0, malformedSignature, 0, decodedSignature.length);
|
||||
|
||||
malformedSignature[i] ^= (0x01 << j);
|
||||
|
||||
try {
|
||||
certificate.verifySignature(signatureBody, Base64.encodeWithPadding(malformedSignature));
|
||||
throw new AssertionError("Signature verification should fail!");
|
||||
} catch (SignatureException e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadChain() throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException, CertPathValidatorException, SignatureException {
|
||||
String certificateChain = URLDecoder.decode("-----BEGIN%20CERTIFICATE-----%0AMIIEoTCCAwmgAwIBAgIJANEHdl0yo7CWMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV%0ABAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwLU2FudGEgQ2xhcmExGjAYBgNV%0ABAoMEUludGVsIENvcnBvcmF0aW9uMTAwLgYDVQQDDCdJbnRlbCBTR1ggQXR0ZXN0%0AYXRpb24gUmVwb3J0IFNpZ25pbmcgQ0EwHhcNMTYxMTIyMDkzNjU4WhcNMjYxMTIw%0AMDkzNjU4WjB7MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExFDASBgNVBAcMC1Nh%0AbnRhIENsYXJhMRowGAYDVQQKDBFJbnRlbCBDb3Jwb3JhdGlvbjEtMCsGA1UEAwwk%0ASW50ZWwgU0dYIEF0dGVzdGF0aW9uIFJlcG9ydCBTaWduaW5nMIIBIjANBgkqhkiG%0A9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqXot4OZuphR8nudFrAFiaGxxkgma/Es/BA%2Bt%0AbeCTUR106AL1ENcWA4FX3K%2BE9BBL0/7X5rj5nIgX/R/1ubhkKWw9gfqPG3KeAtId%0Acv/uTO1yXv50vqaPvE1CRChvzdS/ZEBqQ5oVvLTPZ3VEicQjlytKgN9cLnxbwtuv%0ALUK7eyRPfJW/ksddOzP8VBBniolYnRCD2jrMRZ8nBM2ZWYwnXnwYeOAHV%2BW9tOhA%0AImwRwKF/95yAsVwd21ryHMJBcGH70qLagZ7Ttyt%2B%2BqO/6%2BKAXJuKwZqjRlEtSEz8%0AgZQeFfVYgcwSfo96oSMAzVr7V0L6HSDLRnpb6xxmbPdqNol4tQIDAQABo4GkMIGh%0AMB8GA1UdIwQYMBaAFHhDe3amfrzQr35CN%2Bs1fDuHAVE8MA4GA1UdDwEB/wQEAwIG%0AwDAMBgNVHRMBAf8EAjAAMGAGA1UdHwRZMFcwVaBToFGGT2h0dHA6Ly90cnVzdGVk%0Ac2VydmljZXMuaW50ZWwuY29tL2NvbnRlbnQvQ1JML1NHWC9BdHRlc3RhdGlvblJl%0AcG9ydFNpZ25pbmdDQS5jcmwwDQYJKoZIhvcNAQELBQADggGBAGcIthtcK9IVRz4r%0ARq%2BZKE%2B7k50/OxUsmW8aavOzKb0iCx07YQ9rzi5nU73tME2yGRLzhSViFs/LpFa9%0AlpQL6JL1aQwmDR74TxYGBAIi5f4I5TJoCCEqRHz91kpG6Uvyn2tLmnIdJbPE4vYv%0AWLrtXXfFBSSPD4Afn7%2B3/XUggAlc7oCTizOfbbtOFlYA4g5KcYgS1J2ZAeMQqbUd%0AZseZCcaZZZn65tdqee8UXZlDvx0%2BNdO0LR%2B5pFy%2BjuM0wWbu59MvzcmTXbjsi7HY%0A6zd53Yq5K244fwFHRQ8eOB0IWB%2B4PfM7FeAApZvlfqlKOlLcZL2uyVmzRkyR5yW7%0A2uo9mehX44CiPJ2fse9Y6eQtcfEhMPkmHXI01sN%2BKwPbpA39%2BxOsStjhP9N1Y1a2%0AtQAVo%2ByVgLgV2Hws73Fc0o3wC78qPEA%2Bv2aRs/Be3ZFDgDyghc/1fgU%2B7C%2BP6kbq%0Ad4poyb6IW8KCJbxfMJvkordNOgOUUxndPHEi/tb/U7uLjLOgAA%3D%3D%0A-----END%20CERTIFICATE-----%0A-----BEGIN%20CERTIFICATE-----%0AMIIFSzCCA7OgAwIBAgIJANEHdl0yo7CUMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV%0ABAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwLU2FudGEgQ2xhcmExGjAYBgNV%0ABAoMEUludGVsIENvcnBvcmF0aW9uMTAwLgYDVQQDDCdJbnRlbCBTR1ggQXR0ZXN0%0AYXRpb24gUmVwb3J0IFNpZ25pbmcgQ0EwIBcNMTYxMTE0MTUzNzMxWhgPMjA0OTEy%0AMzEyMzU5NTlaMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwL%0AU2FudGEgQ2xhcmExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0aW9uMTAwLgYDVQQD%0ADCdJbnRlbCBTR1ggQXR0ZXN0YXRpb24gUmVwb3J0IFNpZ25pbmcgQ0EwggGiMA0G%0ACSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCfPGR%2BtXc8u1EtJzLA10Feu1Wg%2Bp7e%0ALmSRmeaCHbkQ1TF3Nwl3RmpqXkeGzNLd69QUnWovYyVSndEMyYc3sHecGgfinEeh%0ArgBJSEdsSJ9FpaFdesjsxqzGRa20PYdnnfWcCTvFoulpbFR4VBuXnnVLVzkUvlXT%0AL/TAnd8nIZk0zZkFJ7P5LtePvykkar7LcSQO85wtcQe0R1Raf/sQ6wYKaKmFgCGe%0ANpEJUmg4ktal4qgIAxk%2BQHUxQE42sxViN5mqglB0QJdUot/o9a/V/mMeH8KvOAiQ%0AbyinkNndn%2BBgk5sSV5DFgF0DffVqmVMblt5p3jPtImzBIH0QQrXJq39AT8cRwP5H%0AafuVeLHcDsRp6hol4P%2BZFIhu8mmbI1u0hH3W/0C2BuYXB5PC%2B5izFFh/nP0lc2Lf%0A6rELO9LZdnOhpL1ExFOq9H/B8tPQ84T3Sgb4nAifDabNt/zu6MmCGo5U8lwEFtGM%0ARoOaX4AS%2B909x00lYnmtwsDVWv9vBiJCXRsCAwEAAaOByTCBxjBgBgNVHR8EWTBX%0AMFWgU6BRhk9odHRwOi8vdHJ1c3RlZHNlcnZpY2VzLmludGVsLmNvbS9jb250ZW50%0AL0NSTC9TR1gvQXR0ZXN0YXRpb25SZXBvcnRTaWduaW5nQ0EuY3JsMB0GA1UdDgQW%0ABBR4Q3t2pn680K9%2BQjfrNXw7hwFRPDAfBgNVHSMEGDAWgBR4Q3t2pn680K9%2BQjfr%0ANXw7hwFRPDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkq%0AhkiG9w0BAQsFAAOCAYEAeF8tYMXICvQqeXYQITkV2oLJsp6J4JAqJabHWxYJHGir%0AIEqucRiJSSx%2BHjIJEUVaj8E0QjEud6Y5lNmXlcjqRXaCPOqK0eGRz6hi%2BripMtPZ%0AsFNaBwLQVV905SDjAzDzNIDnrcnXyB4gcDFCvwDFKKgLRjOB/WAqgscDUoGq5ZVi%0AzLUzTqiQPmULAQaB9c6Oti6snEFJiCQ67JLyW/E83/frzCmO5Ru6WjU4tmsmy8Ra%0AUd4APK0wZTGtfPXU7w%2BIBdG5Ez0kE1qzxGQaL4gINJ1zMyleDnbuS8UicjJijvqA%0A152Sq049ESDz%2B1rRGc2NVEqh1KaGXmtXvqxXcTB%2BLjy5Bw2ke0v8iGngFBPqCTVB%0A3op5KBG3RjbF6RRSzwzuWfL7QErNC8WEy5yDVARzTA5%2BxmBc388v9Dm21HGfcC8O%0ADD%2BgT9sSpssq0ascmvH49MOgjt1yoysLtdCtJW/9FZpoOypaHx0R%2BmJTLwPXVMrv%0ADaVzWh5aiEx%2BidkSGMnX%0A-----END%20CERTIFICATE-----%0A");
|
||||
|
||||
KeyStore keyStore = KeyStore.getInstance("JKS");
|
||||
keyStore.load(getClass().getResourceAsStream("/ias.jks"), "whisper".toCharArray());
|
||||
|
||||
assertThrows(
|
||||
"Should not be a valid chain",
|
||||
CertPathValidatorException.class,
|
||||
() -> new SigningCertificate(certificateChain, keyStore)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user