private byte[] SendInitializationRequest(State state) { // message format: // nonce(16), pubkey(32), ecdh(32), padding(...), signature(64), mac(12) if (!(state is ClientState clientState)) { throw new InvalidOperationException("Only the client can send init request."); } // 16 bytes nonce clientState.InitializationNonce = RandomNumberGenerator.Generate(InitializationNonceSize); // get the public key var pubkey = Signature.GetPublicKey(); // generate new ECDH keypair for init message and root key IKeyAgreement clientEcdh = KeyAgreementFactory.GenerateNew(); clientState.LocalEcdhForInit = clientEcdh; // nonce(16), <pubkey(32), ecdh(32), signature(64)>, mac(12) var initializationMessageSize = InitializationNonceSize + EcNumSize * 4 + MacSize; var messageSize = Math.Max(Configuration.MinimumMessageSize, initializationMessageSize); var initializationMessageSizeWithSignature = messageSize - MacSize; var initializationMessageSizeWithoutSignature = messageSize - MacSize - SignatureSize; var signatureOffset = messageSize - MacSize - SignatureSize; var message = new byte[messageSize]; Array.Copy(clientState.InitializationNonce, 0, message, 0, InitializationNonceSize); Array.Copy(pubkey, 0, message, InitializationNonceSize, EcNumSize); Array.Copy(clientEcdh.GetPublicKey(), 0, message, InitializationNonceSize + EcNumSize, EcNumSize); // sign the message var digest = Digest.ComputeDigest(message, 0, initializationMessageSizeWithoutSignature); Array.Copy(Signature.Sign(digest), 0, message, signatureOffset, SignatureSize); // encrypt the message with the application key var cipher = new AesCtrMode(AesFactory.GetAes(true, Configuration.ApplicationKey), clientState.InitializationNonce); var encryptedPayload = cipher.Process(message, InitializationNonceSize, initializationMessageSizeWithSignature - InitializationNonceSize); Array.Copy(encryptedPayload, 0, message, InitializationNonceSize, initializationMessageSizeWithSignature - InitializationNonceSize); // calculate mac var Mac = new Poly(AesFactory); Mac.Init(Configuration.ApplicationKey, clientState.InitializationNonce, MacSize); Mac.Process(message, 0, initializationMessageSizeWithSignature); var mac = Mac.Compute(); Array.Copy(mac, 0, message, initializationMessageSizeWithSignature, MacSize); return(message); }
private void ReceiveFirstMessage(State state, byte[] payload, EcdhRatchetStep ecdhRatchetStep) { if (!(state is ServerState serverState)) { throw new InvalidOperationException("Only the server can receive the first client message."); } if (serverState.FirstReceiveHeaderKey == null) { throw new InvalidOperationException("Invalid message received"); } // check the first receive header key mac var payloadExceptMac = new ArraySegment <byte>(payload, 0, payload.Length - MacSize); var maciv = new ArraySegment <byte>(payload, 0, 16); var mac = new ArraySegment <byte>(payload, payload.Length - MacSize, MacSize); var Mac = new Poly(AesFactory); Mac.Init(serverState.FirstReceiveHeaderKey, maciv, MacSize); Mac.Process(payloadExceptMac); var compareMac = Mac.Compute(); if (!mac.Matches(compareMac)) { throw new InvalidOperationException("The first received message does not have the correct MAC"); } var data = DeconstructMessage(state, payload, serverState.FirstReceiveHeaderKey, ecdhRatchetStep, false); if (data.Length < InitializationNonceSize || !serverState.NextInitializationNonce.Matches(data, 0, InitializationNonceSize)) { throw new InvalidOperationException("The first received message did not contain the correct payload"); } serverState.FirstSendHeaderKey = null; serverState.FirstReceiveHeaderKey = null; serverState.LocalEcdhRatchetStep0 = null; serverState.LocalEcdhRatchetStep1 = null; serverState.RootKey = null; }
private static int MatchMessageWithMac(ArraySegment <byte> message, IAesFactory aesFactory, params byte[][] keys) { var messageSize = message.Count; var mac = new ArraySegment <byte>(message.Array, message.Offset + messageSize - MacSize, MacSize); var maciv = new ArraySegment <byte>(message.Array, message.Offset, 16); var payloadExceptMac = new ArraySegment <byte>(message.Array, message.Offset, messageSize - MacSize); if (messageSize < MinimumMessageSize) { return(-1); } for (int i = 0; i < keys.Length; i++) { var key = keys[i]; if (key == null) { continue; } if (key.Length != 32) { throw new ArgumentException("Each key must be 32 bytes", nameof(keys)); } var Mac = new Poly(aesFactory); Mac.Init(key, maciv, MacSize); Mac.Process(payloadExceptMac); var compareMac = Mac.Compute(); if (mac.Matches(compareMac)) { return(i); } } return(-1); }
private (byte[] headerKeyUsed, EcdhRatchetStep ratchetUsed, bool usedNextHeaderKey, bool usedApplicationKey) InterpretMessageMAC(State state, byte[] payload, byte[] overrideHeaderKey = null) { // get some basic parts var messageSize = payload.Length; var payloadExceptMac = new ArraySegment <byte>(payload, 0, messageSize - MacSize); var mac = new ArraySegment <byte>(payload, messageSize - MacSize, MacSize); // find the header key by checking the mac var maciv = new byte[16]; Array.Copy(payload, maciv, 16); var Mac = new Poly(AesFactory); var usedNextHeaderKey = false; var usedApplicationHeaderKey = false; byte[] headerKey = null; EcdhRatchetStep ratchetUsed = null; if (overrideHeaderKey != null) { headerKey = overrideHeaderKey; Mac.Init(headerKey, maciv, MacSize); Mac.Process(payloadExceptMac); var compareMac = Mac.Compute(); if (!mac.Matches(compareMac)) { throw new InvalidOperationException("Could not decrypt the incoming message with given header key"); } } else { // if we are initialized check the mac using ratchet receive header keys. if (state.Ratchets != null && !state.Ratchets.IsEmpty) { foreach (EcdhRatchetStep ratchet in state.Ratchets.Enumerate()) { headerKey = ratchet.ReceiveHeaderKey; Mac.Init(headerKey, maciv, MacSize); Mac.Process(payloadExceptMac); var compareMac = Mac.Compute(); if (mac.Matches(compareMac)) { ratchetUsed = ratchet; break; } else if (ratchet.NextReceiveHeaderKey != null) { headerKey = ratchet.NextReceiveHeaderKey; Mac.Init(headerKey, maciv, MacSize); Mac.Process(payloadExceptMac); compareMac = Mac.Compute(); if (mac.Matches(compareMac)) { usedNextHeaderKey = true; ratchetUsed = ratchet; break; } } } } if (ratchetUsed == null) { // we're either not initialized or this is an initialization message. // To determine that we mac using the application key headerKey = Configuration.ApplicationKey; Mac.Init(headerKey, maciv, MacSize); Mac.Process(payloadExceptMac); var compareMac = Mac.Compute(); if (mac.Matches(compareMac)) { usedApplicationHeaderKey = true; } else { headerKey = null; } } } return(headerKey, ratchetUsed, usedNextHeaderKey, usedApplicationHeaderKey); }
private byte[] ConstructMessage(ArraySegment <byte> message, bool includeEcdh, EcdhRatchetStep step) { if (message == null) { throw new ArgumentNullException(nameof(message)); } // message format: // <nonce (4)>, <payload, padding>, mac(12) // <nonce (4), ecdh (32)>, <payload, padding>, mac(12) // get the payload key and nonce (var payloadKey, var messageNumber) = step.SendingChain.RatchetForSending(KeyDerivation); var nonce = BigEndianBitConverter.GetBytes(messageNumber); // make sure the first bit is not set as we use that bit to indicate // the presence of new ECDH parameters if ((nonce[0] & 0b1000_0000) != 0) { throw new InvalidOperationException($"The message number is too big. Cannot encrypt more than {((uint)1 << 31) - 1} messages without exchanging keys"); } // calculate some sizes var headerSize = NonceSize + (includeEcdh ? EcNumSize : 0); var overhead = headerSize + MacSize; var messageSize = message.Count; var maxMessageSize = Configuration.MaximumMessageSize - overhead; var minPayloadSize = Configuration.MinimumMessageSize - overhead; // build the payload: <payload, padding> ArraySegment <byte> payload; if (messageSize < minPayloadSize) { payload = new ArraySegment <byte>(new byte[minPayloadSize]); Array.Copy(message.Array, message.Offset, payload.Array, 0, message.Count); } else if (messageSize > maxMessageSize) { throw new InvalidOperationException("The message doesn't fit inside the MTU"); } else { payload = message; } // encrypt the payload var icipher = new AesCtrMode(AesFactory.GetAes(true, payloadKey), nonce); var encryptedPayload = icipher.Process(payload); // build the header: <nonce(4), ecdh(32)?> var header = new byte[headerSize]; Array.Copy(nonce, header, NonceSize); if (includeEcdh) { var ratchetPublicKey = step.GetPublicKey(); Array.Copy(ratchetPublicKey, 0, header, nonce.Length, ratchetPublicKey.Length); // set the has ecdh bit header[0] |= 0b1000_0000; } else { // clear the has ecdh bit header[0] &= 0b0111_1111; } // encrypt the header using the header key and using the // last 16 bytes of the message as the nonce. var headerEncryptionNonce = new ArraySegment <byte>(encryptedPayload, encryptedPayload.Length - HeaderIVSize, HeaderIVSize); var hcipher = new AesCtrMode(GetHeaderKeyCipher(step.SendHeaderKey), headerEncryptionNonce); var encryptedHeader = hcipher.Process(header); // mac the message: <header>, <payload>, mac(12) // the mac uses the header encryption derived key (all 32 bytes) var Mac = new Poly(AesFactory); var maciv = new byte[16]; var epl = encryptedPayload.Length; var ehl = encryptedHeader.Length; if (ehl < maciv.Length) { Array.Copy(encryptedHeader, maciv, ehl); Array.Copy(encryptedPayload, 0, maciv, ehl, 16 - ehl); } else { Array.Copy(encryptedHeader, maciv, maciv.Length); } Mac.Init(step.SendHeaderKey, maciv, MacSize); Mac.Process(encryptedHeader); Mac.Process(encryptedPayload); var mac = Mac.Compute(); // construct the resulting message var result = new byte[ehl + epl + mac.Length]; Array.Copy(encryptedHeader, 0, result, 0, ehl); Array.Copy(encryptedPayload, 0, result, ehl, epl); Array.Copy(mac, 0, result, ehl + epl, mac.Length); return(result); }
private byte[] SendInitializationResponse(State state, ArraySegment <byte> initializationNonce, ArraySegment <byte> remoteEcdhForInit) { // message format: // new nonce(16), ecdh pubkey(32), // <nonce from init request(16), server pubkey(32), // new ecdh pubkey(32) x2, Padding(...), signature(64)>, mac(12) = 236 bytes if (!(state is ServerState serverState)) { throw new InvalidOperationException("Only the server can send init response."); } // generate a nonce and new ecdh parms var serverNonce = RandomNumberGenerator.Generate(InitializationNonceSize); serverState.NextInitializationNonce = serverNonce; IKeyAgreement rootPreEcdh = KeyAgreementFactory.GenerateNew(); var rootPreEcdhPubkey = rootPreEcdh.GetPublicKey(); // generate server ECDH for root key and root key var rootPreKey = rootPreEcdh.DeriveKey(remoteEcdhForInit); rootPreKey = Digest.ComputeDigest(rootPreKey); var genKeys = KeyDerivation.GenerateKeys(rootPreKey, serverNonce, 3, EcNumSize); serverState.RootKey = genKeys[0]; serverState.FirstSendHeaderKey = genKeys[1]; serverState.FirstReceiveHeaderKey = genKeys[2]; // generate two server ECDH. One for ratchet 0 sending key and one for the next // this is enough for the server to generate a receiving chain key and sending // chain key as soon as the client sends a sending chain key IKeyAgreement serverEcdhRatchet0 = KeyAgreementFactory.GenerateNew(); serverState.LocalEcdhRatchetStep0 = serverEcdhRatchet0; IKeyAgreement serverEcdhRatchet1 = KeyAgreementFactory.GenerateNew(); serverState.LocalEcdhRatchetStep1 = serverEcdhRatchet1; var minimumMessageSize = InitializationNonceSize * 2 + EcNumSize * 6 + MacSize; var entireMessageSize = Math.Max(Configuration.MinimumMessageSize, minimumMessageSize); var macOffset = entireMessageSize - MacSize; var entireMessageWithoutMacOrSignatureSize = macOffset - SignatureSize; var encryptedPayloadOffset = InitializationNonceSize + EcNumSize; var encryptedPayloadSize = macOffset - encryptedPayloadOffset; // construct the message var message = new byte[entireMessageSize]; Array.Copy(serverNonce, 0, message, 0, InitializationNonceSize); Array.Copy(rootPreEcdhPubkey, 0, message, InitializationNonceSize, EcNumSize); // construct the to-be-encrypted part var rre0 = serverEcdhRatchet0.GetPublicKey(); var rre1 = serverEcdhRatchet1.GetPublicKey(); Array.Copy(initializationNonce.Array, initializationNonce.Offset, message, encryptedPayloadOffset, InitializationNonceSize); Array.Copy(Signature.GetPublicKey(), 0, message, encryptedPayloadOffset + InitializationNonceSize, EcNumSize); Array.Copy(rre0, 0, message, encryptedPayloadOffset + InitializationNonceSize + EcNumSize, EcNumSize); Array.Copy(rre1, 0, message, encryptedPayloadOffset + InitializationNonceSize + EcNumSize * 2, EcNumSize); // sign the message var digest = Digest.ComputeDigest(message, 0, entireMessageWithoutMacOrSignatureSize); Array.Copy(Signature.Sign(digest), 0, message, entireMessageWithoutMacOrSignatureSize, SignatureSize); // encrypt the message var cipher = new AesCtrMode(AesFactory.GetAes(true, rootPreKey), serverNonce); var encryptedPayload = cipher.Process(message, encryptedPayloadOffset, encryptedPayloadSize); Array.Copy(encryptedPayload, 0, message, encryptedPayloadOffset, encryptedPayloadSize); // encrypt the header cipher = new AesCtrMode(AesFactory.GetAes(true, Configuration.ApplicationKey), encryptedPayload, encryptedPayloadSize - HeaderIVSize, HeaderIVSize); var encryptedHeader = cipher.Process(message, 0, encryptedPayloadOffset); Array.Copy(encryptedHeader, 0, message, 0, encryptedPayloadOffset); // calculate mac var Mac = new Poly(AesFactory); Mac.Init(Configuration.ApplicationKey, encryptedHeader, 0, InitializationNonceSize, MacSize); Mac.Process(message, 0, macOffset); var mac = Mac.Compute(); Array.Copy(mac, 0, message, macOffset, MacSize); return(message); }