mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 21:15:48 +00:00
Implement new CDS changes.
This commit is contained in:
@@ -73,6 +73,8 @@ import org.whispersystems.signalservice.internal.util.StaticCredentialsProvider;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
import org.whispersystems.util.Base64;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyStore;
|
||||
@@ -363,29 +365,38 @@ public class SignalServiceAccountManager {
|
||||
return activeTokens;
|
||||
}
|
||||
|
||||
public List<String> getRegisteredUsers(KeyStore iasKeyStore, Set<String> e164numbers, String enclaveId)
|
||||
public Map<String, UUID> getRegisteredUsers(KeyStore iasKeyStore, Set<String> e164numbers, String mrenclave)
|
||||
throws IOException, Quote.InvalidQuoteFormatException, UnauthenticatedQuoteException, SignatureException, UnauthenticatedResponseException
|
||||
{
|
||||
try {
|
||||
String authorization = pushServiceSocket.getContactDiscoveryAuthorization();
|
||||
RemoteAttestation remoteAttestation = RemoteAttestationUtil.getAndVerifyRemoteAttestation(pushServiceSocket, PushServiceSocket.ClientSet.ContactDiscovery, iasKeyStore, enclaveId, enclaveId, authorization);
|
||||
List<String> addressBook = new LinkedList<>();
|
||||
String authorization = this.pushServiceSocket.getContactDiscoveryAuthorization();
|
||||
Map<String, RemoteAttestation> attestations = RemoteAttestationUtil.getAndVerifyMultiRemoteAttestation(pushServiceSocket,
|
||||
PushServiceSocket.ClientSet.ContactDiscovery,
|
||||
iasKeyStore,
|
||||
mrenclave,
|
||||
mrenclave,
|
||||
authorization);
|
||||
|
||||
List<String> addressBook = new ArrayList<>(e164numbers.size());
|
||||
|
||||
for (String e164number : e164numbers) {
|
||||
addressBook.add(e164number.substring(1));
|
||||
}
|
||||
|
||||
DiscoveryRequest request = ContactDiscoveryCipher.createDiscoveryRequest(addressBook, remoteAttestation);
|
||||
DiscoveryResponse response = pushServiceSocket.getContactDiscoveryRegisteredUsers(authorization, request, remoteAttestation.getCookies(), enclaveId);
|
||||
byte[] data = ContactDiscoveryCipher.getDiscoveryResponseData(response, remoteAttestation);
|
||||
List<String> cookies = attestations.values().iterator().next().getCookies();
|
||||
DiscoveryRequest request = ContactDiscoveryCipher.createDiscoveryRequest(addressBook, attestations);
|
||||
DiscoveryResponse response = this.pushServiceSocket.getContactDiscoveryRegisteredUsers(authorization, request, cookies, mrenclave);
|
||||
byte[] data = ContactDiscoveryCipher.getDiscoveryResponseData(response, attestations.values());
|
||||
|
||||
Iterator<String> addressBookIterator = addressBook.iterator();
|
||||
List<String> results = new LinkedList<>();
|
||||
HashMap<String, UUID> results = new HashMap<>(addressBook.size());
|
||||
DataInputStream uuidInputStream = new DataInputStream(new ByteArrayInputStream(data));
|
||||
|
||||
for (byte aData : data) {
|
||||
String candidate = addressBookIterator.next();
|
||||
|
||||
if (aData != 0) results.add('+' + candidate);
|
||||
for (String candidate : addressBook) {
|
||||
long candidateUuidHigh = uuidInputStream.readLong();
|
||||
long candidateUuidLow = uuidInputStream.readLong();
|
||||
if (candidateUuidHigh != 0 || candidateUuidLow != 0) {
|
||||
results.put('+' + candidate, new UUID(candidateUuidHigh, candidateUuidLow));
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
@@ -394,38 +405,6 @@ public class SignalServiceAccountManager {
|
||||
}
|
||||
}
|
||||
|
||||
public void reportContactDiscoveryServiceMatch() {
|
||||
try {
|
||||
this.pushServiceSocket.reportContactDiscoveryServiceMatch();
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Request to indicate a contact discovery result match failed. Ignoring.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void reportContactDiscoveryServiceMismatch() {
|
||||
try {
|
||||
this.pushServiceSocket.reportContactDiscoveryServiceMismatch();
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Request to indicate a contact discovery result mismatch failed. Ignoring.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void reportContactDiscoveryServiceAttestationError(String reason) {
|
||||
try {
|
||||
this.pushServiceSocket.reportContactDiscoveryServiceAttestationError(reason);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Request to indicate a contact discovery attestation error failed. Ignoring.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void reportContactDiscoveryServiceUnexpectedError(String reason) {
|
||||
try {
|
||||
this.pushServiceSocket.reportContactDiscoveryServiceUnexpectedError(reason);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Request to indicate a contact discovery unexpected error failed. Ignoring.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<SignalStorageManifest> getStorageManifest(StorageKey storageKey) throws IOException {
|
||||
try {
|
||||
String authToken = this.pushServiceSocket.getStorageAuth();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.whispersystems.signalservice.api.crypto;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
@@ -22,4 +23,13 @@ public final class CryptoUtil {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] sha256(byte[] data) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
return digest.digest(data);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,9 @@ final class AESCipher {
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
|
||||
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(TAG_LENGTH_BITS, iv));
|
||||
cipher.updateAAD(aad);
|
||||
if (aad != null) {
|
||||
cipher.updateAAD(aad);
|
||||
}
|
||||
|
||||
byte[] cipherText = cipher.doFinal(requestData);
|
||||
byte[][] parts = ByteUtil.split(cipherText, cipherText.length - TAG_LENGTH_BYTES, TAG_LENGTH_BYTES);
|
||||
|
||||
@@ -1,40 +1,81 @@
|
||||
package org.whispersystems.signalservice.internal.contacts.crypto;
|
||||
|
||||
import org.whispersystems.libsignal.util.ByteUtil;
|
||||
import org.whispersystems.signalservice.api.crypto.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.ArrayList;
|
||||
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, RemoteAttestation remoteAttestation) {
|
||||
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();
|
||||
|
||||
for (String address : addressBook) {
|
||||
requestDataStream.write(nonce);
|
||||
|
||||
for (String address : addresses) {
|
||||
requestDataStream.write(ByteUtil.longToByteArray(Long.parseLong(address)));
|
||||
}
|
||||
|
||||
byte[] clientKey = remoteAttestation.getKeys().getClientKey();
|
||||
byte[] requestData = requestDataStream.toByteArray();
|
||||
byte[] aad = remoteAttestation.getRequestId();
|
||||
|
||||
AESCipher.AESEncryptedResult aesEncryptedResult = AESCipher.encrypt(clientKey, aad, requestData);
|
||||
|
||||
return new DiscoveryRequest(addressBook.size(), aesEncryptedResult.aad, aesEncryptedResult.iv, aesEncryptedResult.data, aesEncryptedResult.mac);
|
||||
return requestDataStream.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] getDiscoveryResponseData(DiscoveryResponse response, RemoteAttestation remoteAttestation) throws InvalidCiphertextException {
|
||||
return AESCipher.decrypt(remoteAttestation.getKeys().getServerKey(), response.getIv(), response.getData(), response.getMac());
|
||||
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 {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ package org.whispersystems.signalservice.internal.contacts.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.whispersystems.signalservice.internal.util.Hex;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class DiscoveryRequest {
|
||||
|
||||
@@ -27,7 +27,7 @@ public class DiscoveryRequest {
|
||||
private int addressCount;
|
||||
|
||||
@JsonProperty
|
||||
private byte[] requestId;
|
||||
private byte[] commitment;
|
||||
|
||||
@JsonProperty
|
||||
private byte[] iv;
|
||||
@@ -38,20 +38,22 @@ public class DiscoveryRequest {
|
||||
@JsonProperty
|
||||
private byte[] mac;
|
||||
|
||||
public DiscoveryRequest() {
|
||||
@JsonProperty
|
||||
private Map<String, QueryEnvelope> envelopes;
|
||||
|
||||
}
|
||||
public DiscoveryRequest() { }
|
||||
|
||||
public DiscoveryRequest(int addressCount, byte[] requestId, byte[] iv, byte[] data, byte[] mac) {
|
||||
public DiscoveryRequest(int addressCount, byte[] commitment, byte[] iv, byte[] data, byte[] mac, Map<String, QueryEnvelope> envelopes) {
|
||||
this.addressCount = addressCount;
|
||||
this.requestId = requestId;
|
||||
this.commitment = commitment;
|
||||
this.iv = iv;
|
||||
this.data = data;
|
||||
this.mac = mac;
|
||||
this.envelopes = envelopes;
|
||||
}
|
||||
|
||||
public byte[] getRequestId() {
|
||||
return requestId;
|
||||
public byte[] getCommitment() {
|
||||
return commitment;
|
||||
}
|
||||
|
||||
public byte[] getIv() {
|
||||
@@ -70,8 +72,8 @@ public class DiscoveryRequest {
|
||||
return addressCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ addressCount: " + addressCount + ", ticket: " + Hex.toString(requestId) + ", iv: " + Hex.toString(iv) + ", data: " + Hex.toString(data) + ", mac: " + Hex.toString(mac) + "}";
|
||||
return "{ addressCount: " + addressCount + ", envelopes: " + envelopes.size() + " }";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ import org.whispersystems.signalservice.internal.util.Hex;
|
||||
|
||||
public class DiscoveryResponse {
|
||||
|
||||
@JsonProperty
|
||||
private byte[] requestId;
|
||||
|
||||
@JsonProperty
|
||||
private byte[] iv;
|
||||
|
||||
@@ -33,10 +36,8 @@ public class DiscoveryResponse {
|
||||
|
||||
public DiscoveryResponse() {}
|
||||
|
||||
public DiscoveryResponse(byte[] iv, byte[] data, byte[] mac) {
|
||||
this.iv = iv;
|
||||
this.data = data;
|
||||
this.mac = mac;
|
||||
public byte[] getRequestId() {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
public byte[] getIv() {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.whispersystems.signalservice.internal.contacts.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class MultiRemoteAttestationResponse {
|
||||
|
||||
@JsonProperty
|
||||
private Map<String, RemoteAttestationResponse> attestations;
|
||||
|
||||
public Map<String, RemoteAttestationResponse> getAttestations() {
|
||||
return attestations;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.whispersystems.signalservice.internal.contacts.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class QueryEnvelope {
|
||||
|
||||
@JsonProperty
|
||||
private byte[] requestId;
|
||||
|
||||
@JsonProperty
|
||||
private byte[] iv;
|
||||
|
||||
@JsonProperty
|
||||
private byte[] data;
|
||||
|
||||
@JsonProperty
|
||||
private byte[] mac;
|
||||
|
||||
public QueryEnvelope() { }
|
||||
|
||||
public QueryEnvelope(byte[] requestId, byte[] iv, byte[] data, byte[] mac) {
|
||||
this.requestId = requestId;
|
||||
this.iv = iv;
|
||||
this.data = data;
|
||||
this.mac = mac;
|
||||
}
|
||||
}
|
||||
@@ -852,24 +852,6 @@ public class PushServiceSocket {
|
||||
}
|
||||
}
|
||||
|
||||
public void reportContactDiscoveryServiceMatch() throws IOException {
|
||||
makeServiceRequest(String.format(DIRECTORY_FEEDBACK_PATH, "ok"), "PUT", "");
|
||||
}
|
||||
|
||||
public void reportContactDiscoveryServiceMismatch() throws IOException {
|
||||
makeServiceRequest(String.format(DIRECTORY_FEEDBACK_PATH, "mismatch"), "PUT", "");
|
||||
}
|
||||
|
||||
public void reportContactDiscoveryServiceAttestationError(String reason) throws IOException {
|
||||
ContactDiscoveryFailureReason failureReason = new ContactDiscoveryFailureReason(reason);
|
||||
makeServiceRequest(String.format(DIRECTORY_FEEDBACK_PATH, "attestation-error"), "PUT", JsonUtil.toJson(failureReason));
|
||||
}
|
||||
|
||||
public void reportContactDiscoveryServiceUnexpectedError(String reason) throws IOException {
|
||||
ContactDiscoveryFailureReason failureReason = new ContactDiscoveryFailureReason(reason);
|
||||
makeServiceRequest(String.format(DIRECTORY_FEEDBACK_PATH, "unexpected-error"), "PUT", JsonUtil.toJson(failureReason));
|
||||
}
|
||||
|
||||
public TurnServerInfo getTurnServerInfo() throws IOException {
|
||||
String response = makeServiceRequest(TURN_SERVER_INFO, "GET", null);
|
||||
return JsonUtil.fromJson(response, TurnServerInfo.class);
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.whispersystems.signalservice.internal.contacts.crypto.RemoteAttestati
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.RemoteAttestationCipher;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.RemoteAttestationKeys;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedQuoteException;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.MultiRemoteAttestationResponse;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.RemoteAttestationRequest;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.RemoteAttestationResponse;
|
||||
import org.whispersystems.signalservice.internal.util.JsonUtil;
|
||||
@@ -17,8 +18,11 @@ import org.whispersystems.signalservice.internal.util.JsonUtil;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.SignatureException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
@@ -36,33 +40,66 @@ public final class RemoteAttestationUtil {
|
||||
String authorization)
|
||||
throws IOException, Quote.InvalidQuoteFormatException, InvalidCiphertextException, UnauthenticatedQuoteException, SignatureException
|
||||
{
|
||||
Curve25519 curve = Curve25519.getInstance(Curve25519.BEST);
|
||||
Curve25519KeyPair keyPair = curve.generateKeyPair();
|
||||
RemoteAttestationRequest attestationRequest = new RemoteAttestationRequest(keyPair.getPublicKey());
|
||||
Pair<RemoteAttestationResponse, List<String>> attestationResponsePair = getRemoteAttestation(socket, clientSet, authorization, attestationRequest, enclaveName);
|
||||
RemoteAttestationResponse attestationResponse = attestationResponsePair.first();
|
||||
List<String> attestationCookies = attestationResponsePair.second();
|
||||
Curve25519KeyPair keyPair = buildKeyPair();
|
||||
ResponsePair result = makeAttestationRequest(socket, clientSet, authorization, enclaveName, keyPair);
|
||||
RemoteAttestationResponse response = JsonUtil.fromJson(result.body, RemoteAttestationResponse.class);
|
||||
|
||||
RemoteAttestationKeys keys = new RemoteAttestationKeys(keyPair, attestationResponse.getServerEphemeralPublic(), attestationResponse.getServerStaticPublic());
|
||||
Quote quote = new Quote(attestationResponse.getQuote());
|
||||
byte[] requestId = RemoteAttestationCipher.getRequestId(keys, attestationResponse);
|
||||
|
||||
RemoteAttestationCipher.verifyServerQuote(quote, attestationResponse.getServerStaticPublic(), mrenclave);
|
||||
|
||||
RemoteAttestationCipher.verifyIasSignature(iasKeyStore, attestationResponse.getCertificates(), attestationResponse.getSignatureBody(), attestationResponse.getSignature(), quote);
|
||||
|
||||
return new RemoteAttestation(requestId, keys, attestationCookies);
|
||||
return validateAndBuildRemoteAttestation(response, result.cookies, iasKeyStore, keyPair, mrenclave);
|
||||
}
|
||||
|
||||
private static Pair<RemoteAttestationResponse, List<String>> getRemoteAttestation(PushServiceSocket socket,
|
||||
PushServiceSocket.ClientSet clientSet,
|
||||
String authorization,
|
||||
RemoteAttestationRequest request,
|
||||
String enclaveName)
|
||||
throws IOException
|
||||
public static Map<String, RemoteAttestation> getAndVerifyMultiRemoteAttestation(PushServiceSocket socket,
|
||||
PushServiceSocket.ClientSet clientSet,
|
||||
KeyStore iasKeyStore,
|
||||
String enclaveName,
|
||||
String mrenclave,
|
||||
String authorization)
|
||||
throws IOException, Quote.InvalidQuoteFormatException, InvalidCiphertextException, UnauthenticatedQuoteException, SignatureException
|
||||
{
|
||||
Response response = socket.makeRequest(clientSet, authorization, new LinkedList<String>(), "/v1/attestation/" + enclaveName, "PUT", JsonUtil.toJson(request));
|
||||
ResponseBody body = response.body();
|
||||
Curve25519KeyPair keyPair = buildKeyPair();
|
||||
ResponsePair result = makeAttestationRequest(socket, clientSet, authorization, enclaveName, keyPair);
|
||||
MultiRemoteAttestationResponse response = JsonUtil.fromJson(result.body, MultiRemoteAttestationResponse.class);
|
||||
Map<String, RemoteAttestation> attestations = new HashMap<>();
|
||||
|
||||
if (response.getAttestations().isEmpty() || response.getAttestations().size() > 3) {
|
||||
throw new NonSuccessfulResponseCodeException("Incorrect number of attestations: " + response.getAttestations().size());
|
||||
}
|
||||
|
||||
for (Map.Entry<String, RemoteAttestationResponse> entry : response.getAttestations().entrySet()) {
|
||||
attestations.put(entry.getKey(),
|
||||
validateAndBuildRemoteAttestation(entry.getValue(),
|
||||
result.cookies,
|
||||
iasKeyStore,
|
||||
keyPair,
|
||||
mrenclave));
|
||||
}
|
||||
|
||||
return attestations;
|
||||
}
|
||||
|
||||
private static Curve25519KeyPair buildKeyPair() {
|
||||
Curve25519 curve = Curve25519.getInstance(Curve25519.BEST);
|
||||
return curve.generateKeyPair();
|
||||
}
|
||||
|
||||
private static ResponsePair makeAttestationRequest(PushServiceSocket socket,
|
||||
PushServiceSocket.ClientSet clientSet,
|
||||
String authorization,
|
||||
String enclaveName,
|
||||
Curve25519KeyPair keyPair)
|
||||
throws IOException
|
||||
{
|
||||
RemoteAttestationRequest attestationRequest = new RemoteAttestationRequest(keyPair.getPublicKey());
|
||||
Response response = socket.makeRequest(clientSet, authorization, new LinkedList<String>(), "/v1/attestation/" + enclaveName, "PUT", JsonUtil.toJson(attestationRequest));
|
||||
ResponseBody body = response.body();
|
||||
|
||||
if (body == null) {
|
||||
throw new NonSuccessfulResponseCodeException("Empty response!");
|
||||
}
|
||||
|
||||
return new ResponsePair(body.string(), parseCookies(response));
|
||||
}
|
||||
|
||||
private static List<String> parseCookies(Response response) {
|
||||
List<String> rawCookies = response.headers("Set-Cookie");
|
||||
List<String> cookies = new LinkedList<>();
|
||||
|
||||
@@ -70,10 +107,34 @@ public final class RemoteAttestationUtil {
|
||||
cookies.add(cookie.split(";")[0]);
|
||||
}
|
||||
|
||||
if (body != null) {
|
||||
return new Pair<>(JsonUtil.fromJson(body.string(), RemoteAttestationResponse.class), cookies);
|
||||
} else {
|
||||
throw new NonSuccessfulResponseCodeException("Empty response!");
|
||||
return cookies;
|
||||
}
|
||||
|
||||
private static RemoteAttestation validateAndBuildRemoteAttestation(RemoteAttestationResponse response,
|
||||
List<String> cookies,
|
||||
KeyStore iasKeyStore,
|
||||
Curve25519KeyPair keyPair,
|
||||
String mrenclave)
|
||||
throws Quote.InvalidQuoteFormatException, InvalidCiphertextException, UnauthenticatedQuoteException, SignatureException
|
||||
{
|
||||
RemoteAttestationKeys keys = new RemoteAttestationKeys(keyPair, response.getServerEphemeralPublic(), response.getServerStaticPublic());
|
||||
Quote quote = new Quote(response.getQuote());
|
||||
byte[] requestId = RemoteAttestationCipher.getRequestId(keys, response);
|
||||
|
||||
RemoteAttestationCipher.verifyServerQuote(quote, response.getServerStaticPublic(), mrenclave);
|
||||
|
||||
RemoteAttestationCipher.verifyIasSignature(iasKeyStore, response.getCertificates(), response.getSignatureBody(), response.getSignature(), quote);
|
||||
|
||||
return new RemoteAttestation(requestId, keys, cookies);
|
||||
}
|
||||
|
||||
private static class ResponsePair {
|
||||
final String body;
|
||||
final List<String> cookies;
|
||||
|
||||
private ResponsePair(String body, List<String> cookies) {
|
||||
this.body = body;
|
||||
this.cookies = cookies;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user