private (ArraySegment <byte> initializationNonce, ArraySegment <byte> remoteEcdhForInit) ReceiveInitializationRequest(State state, byte[] data) { if (!(state is ServerState serverState)) { throw new InvalidOperationException("Only the server can receive an init request."); } // nonce(16), pubkey(32), ecdh(32), pading(...), signature(64), mac(12) var messageSize = data.Length; var macOffset = messageSize - MacSize; var initializationNonce = new ArraySegment <byte>(data, 0, InitializationNonceSize); CheckNonce(initializationNonce); // decrypt the message var cipher = new AesCtrMode(AesFactory.GetAes(true, Configuration.ApplicationKey), initializationNonce); var decryptedPayload = cipher.Process(data, InitializationNonceSize, macOffset - InitializationNonceSize); Array.Copy(decryptedPayload, 0, data, InitializationNonceSize, decryptedPayload.Length); var clientPublicKeyOffset = InitializationNonceSize; var remoteEcdhOffset = InitializationNonceSize + EcNumSize; var clientPublicKey = new ArraySegment <byte>(data, clientPublicKeyOffset, EcNumSize); var remoteEcdhForInit = new ArraySegment <byte>(data, remoteEcdhOffset, EcNumSize); var signedMessage = new ArraySegment <byte>(data, 0, macOffset); if (serverState.ClientPublicKey != null) { if (!serverState.ClientPublicKey.Matches(clientPublicKey)) { throw new InvalidOperationException("The server was initialized before with a different public key"); } else if (serverState.ClientInitializationNonce != null && initializationNonce.Matches(serverState.ClientInitializationNonce)) { throw new InvalidOperationException("The server was initialized before with the same nonce"); } else { // the client wants to reinitialize. Reset state. serverState.RootKey = null; serverState.FirstSendHeaderKey = null; serverState.FirstReceiveHeaderKey = null; serverState.LocalEcdhRatchetStep0 = null; serverState.LocalEcdhRatchetStep1 = null; serverState.Ratchets.Clear(); } } serverState.ClientInitializationNonce = initializationNonce.ToArray(); IVerifier verifier = VerifierFactory.Create(clientPublicKey); if (!verifier.VerifySignedMessage(Digest, signedMessage)) { throw new InvalidOperationException("The signature was invalid"); } serverState.ClientPublicKey = clientPublicKey.ToArray(); return(initializationNonce, remoteEcdhForInit); }
private void ReceiveInitializationResponse(State state, byte[] data) { if (!(state is ClientState clientState)) { throw new InvalidOperationException("Only the client can receive an init response."); } var messageSize = data.Length; var macOffset = messageSize - MacSize; var headerIvOffset = macOffset - HeaderIVSize; var headerSize = InitializationNonceSize + EcNumSize; var payloadSize = messageSize - headerSize - MacSize; // decrypt header var cipher = new AesCtrMode(AesFactory.GetAes(true, Configuration.ApplicationKey), data, headerIvOffset, HeaderIVSize); var decryptedHeader = cipher.Process(data, 0, headerSize); Array.Copy(decryptedHeader, 0, data, 0, headerSize); // new nonce(16), ecdh pubkey(32), <nonce(16), server pubkey(32), // new ecdh pubkey(32) x2, signature(64)>, mac(12) var nonce = new ArraySegment <byte>(data, 0, InitializationNonceSize); var rootEcdhKey = new ArraySegment <byte>(data, InitializationNonceSize, EcNumSize); var encryptedPayload = new ArraySegment <byte>(data, headerSize, payloadSize); CheckNonce(nonce); // decrypt payload IKeyAgreement rootEcdh = clientState.LocalEcdhForInit; var rootPreKey = rootEcdh.DeriveKey(rootEcdhKey); rootPreKey = Digest.ComputeDigest(rootPreKey); cipher = new AesCtrMode(AesFactory.GetAes(true, rootPreKey), nonce); var decryptedPayload = cipher.Process(encryptedPayload); Array.Copy(decryptedPayload, 0, data, headerSize, payloadSize); // extract some goodies var oldNonce = new ArraySegment <byte>(data, headerSize, InitializationNonceSize); var serverPubKey = new ArraySegment <byte>(data, headerSize + InitializationNonceSize, EcNumSize); var remoteRatchetEcdh0 = new ArraySegment <byte>(data, headerSize + InitializationNonceSize + EcNumSize, EcNumSize); var remoteRatchetEcdh1 = new ArraySegment <byte>(data, headerSize + InitializationNonceSize + EcNumSize * 2, EcNumSize); // make sure the nonce sent back by the server (which is encrypted and signed) // matches the nonce we sent previously if (!oldNonce.Matches(clientState.InitializationNonce)) { throw new InvalidOperationException("Nonce did not match"); } // verify that the signature matches IVerifier verifier = VerifierFactory.Create(serverPubKey); if (!verifier.VerifySignedMessage(Digest, new ArraySegment <byte>(data, 0, payloadSize + headerSize))) { throw new InvalidOperationException("The signature was invalid"); } // keep the server public key around clientState.ServerPublicKey = serverPubKey.ToArray(); // store the new nonce we got from the server clientState.InitializationNonce = nonce.ToArray(); Log.Verbose($"storing iniitlizaionta nonce: {Log.ShowBytes(nonce)}"); // we now have enough information to construct our double ratchet IKeyAgreement localStep0EcdhRatchet = KeyAgreementFactory.GenerateNew(); IKeyAgreement localStep1EcdhRatchet = KeyAgreementFactory.GenerateNew(); // initialize client root key and ecdh ratchet var genKeys = KeyDerivation.GenerateKeys(rootPreKey, clientState.InitializationNonce, 3, 32); var rootKey = genKeys[0]; var receiveHeaderKey = genKeys[1]; var sendHeaderKey = genKeys[2]; clientState.Ratchets.Add(EcdhRatchetStep.InitializeClient(KeyDerivation, Digest, rootKey, remoteRatchetEcdh0, remoteRatchetEcdh1, localStep0EcdhRatchet, receiveHeaderKey, sendHeaderKey, localStep1EcdhRatchet)); clientState.LocalEcdhForInit = null; }