in fizz/server/ServerProtocol.cpp [1085:1665]
return runOnCallerIfComplete(
state.executor(),
std::move(results),
[&state,
chlo = std::move(chlo),
cookieState = std::move(cookieState),
version = *version,
cipher,
pskMode = resStateResult.pskMode,
obfuscatedAge =
resStateResult.obfuscatedAge](FutureResultType result) mutable {
auto& resumption = *std::get<0>(result);
auto pskType = resumption.first;
auto resState = std::move(resumption.second);
auto replayCacheResult = *std::get<1>(result);
if (resState) {
if (!validateResumptionState(*resState, *pskMode, version, cipher)) {
pskType = PskType::Rejected;
pskMode = folly::none;
resState = folly::none;
}
} else {
pskMode = folly::none;
}
auto legacySessionId = chlo.legacy_session_id->clone();
// If we successfully resumed, set the handshake time to the ticket's
// handshake time to preserve it across ticket updates. If not, set it
// to now.
std::chrono::system_clock::time_point handshakeTime;
if (resState) {
handshakeTime = resState->handshakeTime;
} else {
handshakeTime = state.context()->getClock().getCurrentTime();
}
std::unique_ptr<KeyScheduler> scheduler;
std::unique_ptr<HandshakeContext> handshakeContext;
std::tie(scheduler, handshakeContext) = setupSchedulerAndContext(
*state.context()->getFactory(),
cipher,
chlo,
resState,
cookieState,
pskType,
std::move(state.handshakeContext()),
version);
if (state.cipher().has_value() && cipher != *state.cipher()) {
throw FizzException(
"cipher mismatch with previous negotiation",
AlertDescription::illegal_parameter);
}
auto alpn = negotiateAlpn(chlo, folly::none, *state.context());
auto clockSkew = getClockSkew(
resState,
obfuscatedAge,
state.context()->getClock().getCurrentTime());
auto appToken = getAppToken(resState);
auto earlyDataType = negotiateEarlyDataType(
state.context()->getAcceptEarlyData(version),
chlo,
resState,
cipher,
state.keyExchangeType(),
cookieState,
alpn,
replayCacheResult,
clockSkew,
state.context()->getClockSkewTolerance(),
state.appTokenValidator());
std::unique_ptr<EncryptedReadRecordLayer> earlyReadRecordLayer;
Buf earlyExporterMaster;
folly::Optional<SecretAvailable> earlyReadSecretAvailable;
if (earlyDataType == EarlyDataType::Accepted) {
auto earlyContext = handshakeContext->getHandshakeContext();
auto earlyReadSecret = scheduler->getSecret(
EarlySecrets::ClientEarlyTraffic, earlyContext->coalesce());
if (!state.context()->getOmitEarlyRecordLayer()) {
earlyReadRecordLayer =
state.context()->getFactory()->makeEncryptedReadRecordLayer(
EncryptionLevel::EarlyData);
earlyReadRecordLayer->setProtocolVersion(version);
Protocol::setAead(
*earlyReadRecordLayer,
cipher,
folly::range(earlyReadSecret.secret),
*state.context()->getFactory(),
*scheduler);
}
earlyReadSecretAvailable =
SecretAvailable(std::move(earlyReadSecret));
earlyExporterMaster = folly::IOBuf::copyBuffer(
scheduler
->getSecret(
EarlySecrets::EarlyExporter, earlyContext->coalesce())
.secret);
}
Optional<NamedGroup> group;
Optional<Buf> serverShare;
KeyExchangeType keyExchangeType;
if (!pskMode || *pskMode != PskKeyExchangeMode::psk_ke) {
Optional<Buf> clientShare;
std::tie(group, clientShare) = negotiateGroup(
version, chlo, state.context()->getSupportedGroups());
if (!clientShare) {
VLOG(8) << "Did not find key share for " << toString(*group);
if (state.group().has_value() || cookieState) {
throw FizzException(
"key share not found for already negotiated group",
AlertDescription::illegal_parameter);
}
// If we were otherwise going to accept early data we now need to
// reject it. It's a little ugly to change our previous early data
// decision, but doing it this way allows us to move the key
// schedule forward as we do the key exchange.
if (earlyDataType == EarlyDataType::Accepted) {
earlyDataType = EarlyDataType::Rejected;
}
message_hash chloHash;
chloHash.hash = handshakeContext->getHandshakeContext();
handshakeContext =
state.context()->getFactory()->makeHandshakeContext(cipher);
handshakeContext->appendToTranscript(
encodeHandshake(std::move(chloHash)));
auto encodedHelloRetryRequest = getHelloRetryRequest(
version,
cipher,
*group,
legacySessionId ? legacySessionId->clone() : nullptr,
*handshakeContext);
WriteToSocket serverFlight;
serverFlight.contents.emplace_back(
state.writeRecordLayer()->writeHandshake(
std::move(encodedHelloRetryRequest)));
if (legacySessionId && !legacySessionId->empty()) {
TLSContent writeCCS;
writeCCS.encryptionLevel = EncryptionLevel::Plaintext;
writeCCS.contentType = ContentType::change_cipher_spec;
writeCCS.data = folly::IOBuf::wrapBuffer(FakeChangeCipherSpec);
serverFlight.contents.emplace_back(std::move(writeCCS));
}
// Create a new record layer in case we need to skip early data.
auto newReadRecordLayer =
state.context()->getFactory()->makePlaintextReadRecordLayer();
newReadRecordLayer->setSkipEncryptedRecords(
earlyDataType == EarlyDataType::Rejected);
return SemiFuture<Actions>(actions(
MutateState([handshakeContext = std::move(handshakeContext),
version,
cipher,
group,
earlyDataType,
replayCacheResult,
newReadRecordLayer = std::move(
newReadRecordLayer)](State& newState) mutable {
// Save some information about the current state to be
// validated when we get the second client hello. We don't
// validate that the second client hello matches the first
// as strictly as we could according to the spec however.
newState.handshakeContext() = std::move(handshakeContext);
newState.version() = version;
newState.cipher() = cipher;
newState.group() = group;
newState.keyExchangeType() =
KeyExchangeType::HelloRetryRequest;
newState.earlyDataType() = earlyDataType;
newState.replayCacheResult() = replayCacheResult;
newState.readRecordLayer() = std::move(newReadRecordLayer);
}),
std::move(serverFlight),
MutateState(&Transition<StateEnum::ExpectingClientHello>)));
}
if (state.keyExchangeType().has_value()) {
keyExchangeType = *state.keyExchangeType();
} else {
keyExchangeType = KeyExchangeType::OneRtt;
}
serverShare = doKex(
*state.context()->getFactory(), *group, *clientShare, *scheduler);
} else {
keyExchangeType = KeyExchangeType::None;
scheduler->deriveHandshakeSecret();
}
std::vector<Extension> additionalExtensions;
if (state.extensions()) {
additionalExtensions = state.extensions()->getExtensions(chlo);
}
if (state.group().has_value() && (!group || *group != *state.group())) {
throw FizzException(
"group mismatch with previous negotiation",
AlertDescription::illegal_parameter);
}
// Cookies are not required to have already negotiated the group but if
// they did it must match (psk_ke is still allowed as we may not know if
// we are accepting the psk when sending the cookie).
if (cookieState && cookieState->group && group &&
*group != *cookieState->group) {
throw FizzException(
"group mismatch with cookie",
AlertDescription::illegal_parameter);
}
auto encodedServerHello = getServerHello(
version,
state.context()->getFactory()->makeRandom(),
cipher,
resState.has_value(),
group,
std::move(serverShare),
legacySessionId ? legacySessionId->clone() : nullptr,
*handshakeContext);
// Derive handshake keys.
auto handshakeWriteRecordLayer =
state.context()->getFactory()->makeEncryptedWriteRecordLayer(
EncryptionLevel::Handshake);
handshakeWriteRecordLayer->setProtocolVersion(version);
auto handshakeWriteSecret = scheduler->getSecret(
HandshakeSecrets::ServerHandshakeTraffic,
handshakeContext->getHandshakeContext()->coalesce());
Protocol::setAead(
*handshakeWriteRecordLayer,
cipher,
folly::range(handshakeWriteSecret.secret),
*state.context()->getFactory(),
*scheduler);
auto handshakeReadRecordLayer =
state.context()->getFactory()->makeEncryptedReadRecordLayer(
EncryptionLevel::Handshake);
handshakeReadRecordLayer->setProtocolVersion(version);
handshakeReadRecordLayer->setSkipFailedDecryption(
earlyDataType == EarlyDataType::Rejected);
auto handshakeReadSecret = scheduler->getSecret(
HandshakeSecrets::ClientHandshakeTraffic,
handshakeContext->getHandshakeContext()->coalesce());
Protocol::setAead(
*handshakeReadRecordLayer,
cipher,
folly::range(handshakeReadSecret.secret),
*state.context()->getFactory(),
*scheduler);
auto clientHandshakeSecret =
folly::IOBuf::copyBuffer(handshakeReadSecret.secret);
auto encodedEncryptedExt = getEncryptedExt(
*handshakeContext,
alpn,
earlyDataType,
std::move(additionalExtensions));
/*
* Determine we are requesting client auth.
* If yes, add CertificateRequest to handshake write and transcript.
*/
bool requestClientAuth =
state.context()->getClientAuthMode() != ClientAuthMode::None &&
!resState;
Optional<Buf> encodedCertRequest;
if (requestClientAuth) {
encodedCertRequest = getCertificateRequest(
state.context()->getSupportedSigSchemes(),
state.context()->getClientCertVerifier().get(),
*handshakeContext);
}
/*
* Set the cert and signature scheme we are using.
* If sending new cert, add Certificate to handshake write and
* transcript.
*/
Optional<Buf> encodedCertificate;
SemiFuture<Optional<Buf>> signature = folly::none;
Optional<SignatureScheme> sigScheme;
Optional<std::shared_ptr<const Cert>> serverCert;
std::shared_ptr<const Cert> clientCert;
Optional<CertificateCompressionAlgorithm> certCompressionAlgo;
if (!resState) { // TODO or reauth
std::shared_ptr<const SelfCert> originalSelfCert;
std::tie(originalSelfCert, sigScheme) =
chooseCert(*state.context(), chlo);
std::tie(encodedCertificate, certCompressionAlgo) = getCertificate(
originalSelfCert, *state.context(), chlo, *handshakeContext);
auto toBeSigned = handshakeContext->getHandshakeContext();
auto asyncSelfCert =
dynamic_cast<const AsyncSelfCert*>(originalSelfCert.get());
if (asyncSelfCert) {
signature = asyncSelfCert->signFuture(
*sigScheme,
CertificateVerifyContext::Server,
toBeSigned->coalesce());
} else {
signature =
folly::makeSemiFuture<Optional<Buf>>(originalSelfCert->sign(
*sigScheme,
CertificateVerifyContext::Server,
toBeSigned->coalesce()));
}
serverCert = std::move(originalSelfCert);
} else {
serverCert = std::move(resState->serverCert);
clientCert = std::move(resState->clientCert);
}
auto clientRandom = std::move(chlo.random);
return runOnCallerIfComplete(
state.executor(),
std::move(signature),
[&state,
scheduler = std::move(scheduler),
handshakeContext = std::move(handshakeContext),
cipher,
clientRandom = std::move(clientRandom),
group,
encodedServerHello = std::move(encodedServerHello),
handshakeWriteRecordLayer = std::move(handshakeWriteRecordLayer),
handshakeWriteSecret = std::move(handshakeWriteSecret),
handshakeReadRecordLayer = std::move(handshakeReadRecordLayer),
handshakeReadSecret = std::move(handshakeReadSecret),
earlyReadRecordLayer = std::move(earlyReadRecordLayer),
earlyReadSecretAvailable = std::move(earlyReadSecretAvailable),
earlyExporterMaster = std::move(earlyExporterMaster),
clientHandshakeSecret = std::move(clientHandshakeSecret),
encodedEncryptedExt = std::move(encodedEncryptedExt),
encodedCertificate = std::move(encodedCertificate),
encodedCertRequest = std::move(encodedCertRequest),
requestClientAuth,
pskType,
pskMode,
sigScheme,
version,
keyExchangeType,
earlyDataType,
replayCacheResult,
serverCert = std::move(serverCert),
clientCert = std::move(clientCert),
alpn = std::move(alpn),
clockSkew,
appToken = std::move(appToken),
legacySessionId = std::move(legacySessionId),
serverCertCompAlgo = certCompressionAlgo,
handshakeTime](Optional<Buf> sig) mutable {
Optional<Buf> encodedCertificateVerify;
if (sig) {
encodedCertificateVerify = getCertificateVerify(
*sigScheme, std::move(*sig), *handshakeContext);
}
auto encodedFinished = Protocol::getFinished(
folly::range(handshakeWriteSecret.secret), *handshakeContext);
folly::IOBufQueue combined;
if (encodedCertificate) {
if (encodedCertRequest) {
combined.append(std::move(encodedEncryptedExt));
combined.append(std::move(*encodedCertRequest));
combined.append(std::move(*encodedCertificate));
combined.append(std::move(*encodedCertificateVerify));
combined.append(std::move(encodedFinished));
} else {
combined.append(std::move(encodedEncryptedExt));
combined.append(std::move(*encodedCertificate));
combined.append(std::move(*encodedCertificateVerify));
combined.append(std::move(encodedFinished));
}
} else {
combined.append(std::move(encodedEncryptedExt));
combined.append(std::move(encodedFinished));
}
// Some middleboxes appear to break if the first encrypted record
// is larger than ~1300 bytes (likely if it does not fit in the
// first packet).
auto serverEncrypted = handshakeWriteRecordLayer->writeHandshake(
combined.splitAtMost(1000));
if (!combined.empty()) {
auto splitRecord =
handshakeWriteRecordLayer->writeHandshake(combined.move());
// Split record must have the same encryption level as the main
// handshake.
DCHECK(
splitRecord.encryptionLevel ==
serverEncrypted.encryptionLevel);
serverEncrypted.data->prependChain(std::move(splitRecord.data));
}
WriteToSocket serverFlight;
serverFlight.contents.emplace_back(
state.writeRecordLayer()->writeHandshake(
std::move(encodedServerHello)));
if (legacySessionId && !legacySessionId->empty()) {
TLSContent ccsWrite;
ccsWrite.encryptionLevel = EncryptionLevel::Plaintext;
ccsWrite.contentType = ContentType::change_cipher_spec;
ccsWrite.data = folly::IOBuf::wrapBuffer(FakeChangeCipherSpec);
serverFlight.contents.emplace_back(std::move(ccsWrite));
}
serverFlight.contents.emplace_back(std::move(serverEncrypted));
scheduler->deriveMasterSecret();
auto clientFinishedContext =
handshakeContext->getHandshakeContext();
auto exporterMasterVector = scheduler->getSecret(
MasterSecrets::ExporterMaster,
clientFinishedContext->coalesce());
auto exporterMaster = folly::IOBuf::copyBuffer(
folly::range(exporterMasterVector.secret));
scheduler->deriveAppTrafficSecrets(
clientFinishedContext->coalesce());
auto appTrafficWriteRecordLayer =
state.context()->getFactory()->makeEncryptedWriteRecordLayer(
EncryptionLevel::AppTraffic);
appTrafficWriteRecordLayer->setProtocolVersion(version);
auto writeSecret =
scheduler->getSecret(AppTrafficSecrets::ServerAppTraffic);
Protocol::setAead(
*appTrafficWriteRecordLayer,
cipher,
folly::range(writeSecret.secret),
*state.context()->getFactory(),
*scheduler);
// If we have previously dealt with early data (before a
// HelloRetryRequest), don't overwrite the previous result.
auto earlyDataTypeSave = state.earlyDataType()
? *state.earlyDataType()
: earlyDataType;
SecretAvailable handshakeReadSecretAvailable(
std::move(handshakeReadSecret));
SecretAvailable handshakeWriteSecretAvailable(
std::move(handshakeWriteSecret));
SecretAvailable appWriteSecretAvailable(std::move(writeSecret));
// Save all the necessary state except for the read record layer,
// which is done separately as it varies if early data was
// accepted.
MutateState saveState(
[appTrafficWriteRecordLayer =
std::move(appTrafficWriteRecordLayer),
handshakeContext = std::move(handshakeContext),
scheduler = std::move(scheduler),
exporterMaster = std::move(exporterMaster),
serverCert = std::move(serverCert),
clientCert = std::move(clientCert),
cipher,
group,
sigScheme,
clientHandshakeSecret = std::move(clientHandshakeSecret),
pskType,
pskMode,
version,
keyExchangeType,
alpn = std::move(alpn),
earlyDataTypeSave,
replayCacheResult,
clockSkew,
appToken = std::move(appToken),
serverCertCompAlgo,
clientRandom = std::move(clientRandom),
handshakeTime =
std::move(handshakeTime)](State& newState) mutable {
newState.writeRecordLayer() =
std::move(appTrafficWriteRecordLayer);
newState.handshakeContext() = std::move(handshakeContext);
newState.keyScheduler() = std::move(scheduler);
newState.exporterMasterSecret() = std::move(exporterMaster);
newState.serverCert() = std::move(*serverCert);
newState.clientCert() = std::move(clientCert);
newState.version() = version;
newState.cipher() = cipher;
newState.group() = group;
newState.sigScheme() = sigScheme;
newState.clientHandshakeSecret() =
std::move(clientHandshakeSecret);
newState.pskType() = pskType;
newState.pskMode() = pskMode;
newState.keyExchangeType() = keyExchangeType;
newState.earlyDataType() = earlyDataTypeSave;
newState.replayCacheResult() = replayCacheResult;
newState.alpn() = std::move(alpn);
newState.clientClockSkew() = clockSkew;
newState.appToken() = std::move(appToken);
newState.serverCertCompAlgo() = serverCertCompAlgo;
newState.handshakeTime() = std::move(handshakeTime);
newState.clientRandom() = std::move(clientRandom);
});
if (earlyDataType == EarlyDataType::Accepted) {
if (state.context()->getOmitEarlyRecordLayer()) {
return actions(
MutateState([handshakeReadRecordLayer =
std::move(handshakeReadRecordLayer),
earlyExporterMaster =
std::move(earlyExporterMaster)](
State& newState) mutable {
newState.readRecordLayer() =
std::move(handshakeReadRecordLayer);
newState.earlyExporterMasterSecret() =
std::move(earlyExporterMaster);
}),
std::move(saveState),
std::move(*earlyReadSecretAvailable),
std::move(handshakeReadSecretAvailable),
std::move(handshakeWriteSecretAvailable),
std::move(appWriteSecretAvailable),
std::move(serverFlight),
MutateState(&Transition<StateEnum::ExpectingFinished>),
ReportEarlyHandshakeSuccess());
} else {
return actions(
MutateState([handshakeReadRecordLayer =
std::move(handshakeReadRecordLayer),
earlyReadRecordLayer =
std::move(earlyReadRecordLayer),
earlyExporterMaster =
std::move(earlyExporterMaster)](
State& newState) mutable {
newState.readRecordLayer() =
std::move(earlyReadRecordLayer);
newState.handshakeReadRecordLayer() =
std::move(handshakeReadRecordLayer);
newState.earlyExporterMasterSecret() =
std::move(earlyExporterMaster);
}),
std::move(saveState),
std::move(*earlyReadSecretAvailable),
std::move(handshakeReadSecretAvailable),
std::move(handshakeWriteSecretAvailable),
std::move(appWriteSecretAvailable),
std::move(serverFlight),
MutateState(&Transition<StateEnum::AcceptingEarlyData>),
ReportEarlyHandshakeSuccess());
}
} else {
auto transition = requestClientAuth
? Transition<StateEnum::ExpectingCertificate>
: Transition<StateEnum::ExpectingFinished>;
return actions(
MutateState([handshakeReadRecordLayer =
std::move(handshakeReadRecordLayer)](
State& newState) mutable {
newState.readRecordLayer() =
std::move(handshakeReadRecordLayer);
}),
std::move(saveState),
std::move(handshakeReadSecretAvailable),
std::move(handshakeWriteSecretAvailable),
std::move(appWriteSecretAvailable),
std::move(serverFlight),
MutateState(transition));
}
});
});