Remove unused code.

This commit is contained in:
Alex Bakon
2025-05-22 09:35:44 -04:00
committed by Cody Henthorne
parent 15aa941436
commit ea0b8fc594
29 changed files with 2 additions and 1379 deletions

View File

@@ -70,6 +70,8 @@ wire {
protoPath {
srcDir("${project.rootDir}/libsignal-service/src/main/protowire")
}
// Handled by libsignal
prune("signalservice.DecryptionErrorMessage")
}
ktlint {

View File

@@ -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> {

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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;
}
}
}

View File

@@ -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 {
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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 {
}

View File

@@ -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;
}

View File

@@ -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)
);
}
}