141 lines
7.2 KiB
Java
141 lines
7.2 KiB
Java
package org.whispersystems.signalservice.internal.push;
|
|
|
|
import org.whispersystems.curve25519.Curve25519;
|
|
import org.whispersystems.curve25519.Curve25519KeyPair;
|
|
import org.whispersystems.libsignal.util.Pair;
|
|
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
|
|
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
|
import org.whispersystems.signalservice.internal.contacts.crypto.Quote;
|
|
import org.whispersystems.signalservice.internal.contacts.crypto.RemoteAttestation;
|
|
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;
|
|
|
|
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;
|
|
|
|
public final class RemoteAttestationUtil {
|
|
|
|
private RemoteAttestationUtil() {
|
|
}
|
|
|
|
public static RemoteAttestation getAndVerifyRemoteAttestation(PushServiceSocket socket,
|
|
PushServiceSocket.ClientSet clientSet,
|
|
KeyStore iasKeyStore,
|
|
String enclaveName,
|
|
String mrenclave,
|
|
String authorization)
|
|
throws IOException, Quote.InvalidQuoteFormatException, InvalidCiphertextException, UnauthenticatedQuoteException, SignatureException
|
|
{
|
|
Curve25519KeyPair keyPair = buildKeyPair();
|
|
ResponsePair result = makeAttestationRequest(socket, clientSet, authorization, enclaveName, keyPair);
|
|
RemoteAttestationResponse response = JsonUtil.fromJson(result.body, RemoteAttestationResponse.class);
|
|
|
|
return validateAndBuildRemoteAttestation(response, result.cookies, iasKeyStore, keyPair, mrenclave);
|
|
}
|
|
|
|
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
|
|
{
|
|
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<>();
|
|
|
|
for (String cookie : rawCookies) {
|
|
cookies.add(cookie.split(";")[0]);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|