protected override IKeyAgreement StartKeyAgreement() { IKeyAgreement agreement = null; switch (random.Next(4)) { case 0: agreement = new BCryptDiffieHellmanOakleyGroup14(); break; case 1: agreement = new BCryptDiffieHellmanOakleyGroup2(); break; case 2: agreement = new ManagedDiffieHellmanOakley14(); break; case 3: agreement = new ManagedDiffieHellmanOakley2(); break; } W($"DH Type: {agreement.GetType()}"); if (agreement == null) { throw new ArgumentException("How did it get here?"); } return(agreement); }
private async Task <IKeyAgreement> FromDiffieHellmanDomainParametersAsync(KrbSubjectPublicKeyInfo clientPublicValue) { var parameters = KrbDiffieHellmanDomainParameters.DecodeSpecial(clientPublicValue.Algorithm.Parameters.Value); IKeyAgreement agreement = null; switch (parameters.P.Length) { case 128: agreement = CryptoPal.Platform.DiffieHellmanModp2( await Service.Principals.RetrieveKeyCache(KeyAgreementAlgorithm.DiffieHellmanModp2) ); break; case 256: agreement = CryptoPal.Platform.DiffieHellmanModp14( await Service.Principals.RetrieveKeyCache(KeyAgreementAlgorithm.DiffieHellmanModp14) ); break; default: throw new InvalidOperationException("Unknown key agreement parameter"); } var publicKey = DiffieHellmanKey.ParsePublicKey(clientPublicValue.SubjectPublicKey, agreement.PublicKey.KeyLength); agreement.ImportPartnerKey(publicKey); return(agreement); }
public static EcdhRatchetStep InitializeServer(IKeyDerivation kdf, IDigest digest, IKeyAgreement previousKeyPair, byte[] rootKey, ArraySegment <byte> remotePublicKey, IKeyAgreement keyPair, byte[] receiveHeaderKey, byte[] sendHeaderKey) { if (receiveHeaderKey.Length != 32 || sendHeaderKey.Length != 32) { throw new InvalidOperationException("Keys need to be 32 bytes."); } Log.Verbose($"--Initialize ECDH Ratchet"); Log.Verbose($"Root Key: {Log.ShowBytes(rootKey)}"); Log.Verbose($"Prev ECDH Private: ({Log.ShowBytes(previousKeyPair.GetPublicKey())})"); Log.Verbose($"ECDH Public: {Log.ShowBytes(remotePublicKey)}"); Log.Verbose($"Curr ECDH Private: ({Log.ShowBytes(keyPair.GetPublicKey())})"); var e = new EcdhRatchetStep { EcdhKey = keyPair, ReceiveHeaderKey = receiveHeaderKey, SendHeaderKey = sendHeaderKey }; // receive chain Log.Verbose(" --Receiving Chain"); var rcderived = previousKeyPair.DeriveKey(remotePublicKey); rcderived = digest.ComputeDigest(rcderived); Log.Verbose($" C Input Key: {Log.ShowBytes(rootKey)}"); Log.Verbose($" C Key Info: {Log.ShowBytes(rcderived)}"); var rckeys = kdf.GenerateKeys(rcderived, rootKey, 3, 32); Log.Verbose($" C Key Out 0: {Log.ShowBytes(rckeys[0])}"); Log.Verbose($" C Key Out 1: {Log.ShowBytes(rckeys[1])}"); Log.Verbose($" C Key Out 2: {Log.ShowBytes(rckeys[2])}"); rootKey = rckeys[0]; e.ReceivingChain.Initialize(rckeys[1]); e.NextReceiveHeaderKey = rckeys[2]; // send chain Log.Verbose(" --Sending Chain"); var scderived = keyPair.DeriveKey(remotePublicKey); scderived = digest.ComputeDigest(scderived); Log.Verbose($" C Input Key: {Log.ShowBytes(rootKey)}"); Log.Verbose($" C Key Info: {Log.ShowBytes(scderived)}"); var sckeys = kdf.GenerateKeys(scderived, rootKey, 3, 32); Log.Verbose($" C Key Out 0: {Log.ShowBytes(sckeys[0])}"); Log.Verbose($" C Key Out 1: {Log.ShowBytes(sckeys[1])}"); Log.Verbose($" C Key Out 2: {Log.ShowBytes(sckeys[2])}"); rootKey = sckeys[0]; e.SendingChain.Initialize(sckeys[1]); e.NextSendHeaderKey = sckeys[2]; // next root key Log.Verbose($"Next Root Key: ({Log.ShowBytes(rootKey)})"); e.NextRootKey = rootKey; return(e); }
/// <summary> /// Applies credential-specific changes to the KDC-REQ message and is what supplies the PKINIT properties to the request. /// </summary> /// <param name="req">The <see cref="KrbKdcReq"/> that will be modified.</param> public override void TransformKdcReq(KrbKdcReq req) { agreement = StartKeyAgreement(); // We don't support the straight RSA mode because // it doesn't rely on ephemeral key agreement // which isn't great security-wise if (agreement == null) { throw OnlyKeyAgreementSupportedException(); } var padata = req.PaData.ToList(); KrbAuthPack authPack; if (SupportsEllipticCurveDiffieHellman) { authPack = CreateEllipticCurveDiffieHellmanAuthPack(req.Body); } else if (SupportsDiffieHellman) { authPack = CreateDiffieHellmanAuthPack(req.Body); } else { throw OnlyKeyAgreementSupportedException(); } KerberosConstants.Now(out authPack.PKAuthenticator.CTime, out authPack.PKAuthenticator.CuSec); SignedCms signed = new SignedCms( new ContentInfo( IdPkInitAuthData, authPack.Encode().ToArray() ) ); var signer = new CmsSigner(Certificate) { IncludeOption = IncludeOption }; signed.ComputeSignature(signer, silent: true); var pk = new KrbPaPkAsReq { SignedAuthPack = signed.Encode() }; padata.Add(new KrbPaData { Type = PaDataType.PA_PK_AS_REQ, Value = pk.Encode() }); req.PaData = padata.ToArray(); }
protected override bool CacheKeyAgreementParameters(IKeyAgreement agreement) { var serializedPk = JsonConvert.SerializeObject(ConvertKey(agreement.PrivateKey as DiffieHellmanKey)); using (var reg = Registry.CurrentUser.CreateSubKey($"SOFTWARE\\Kerberos.NET\\{UserName}")) { reg.SetValue("DHParameter", serializedPk, RegistryValueKind.String); } return(true); }
protected override void Dispose(bool disposing) { if (disposing) { InitializationNonce?.Shred(); InitializationNonce = null; (LocalEcdhForInit as IDisposable)?.Dispose(); LocalEcdhForInit = null; } }
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 LoadInternal(Stream memory, IKeyAgreementFactory kexFac) { var versionInt = memory.ReadByte(); if (versionInt < 0) { throw new EndOfStreamException(); } var versionByte = (byte)versionInt; bool isClient = (versionByte & 0b0000_1000) != 0; bool hasInit = (versionByte & 0b0001_0000) != 0; bool hasRatchet = (versionByte & 0b0010_0000) != 0; bool hasEcdh = (versionByte & 0b0100_0000) != 0; bool hasServerPublicKey = (versionByte & 0b1000_0000) != 0; if (!isClient) { throw new InvalidOperationException("The provided state is not client state"); } if (hasInit) { if (InitializationNonce == null || InitializationNonce.Length != MicroRatchetContext.InitializationNonceSize) { InitializationNonce = new byte[MicroRatchetContext.InitializationNonceSize]; } memory.Read(InitializationNonce, 0, MicroRatchetContext.InitializationNonceSize); } if (hasEcdh) { LocalEcdhForInit = kexFac.Deserialize(memory); } if (hasServerPublicKey) { if (ServerPublicKey == null || ServerPublicKey.Length != MicroRatchetContext.ExpectedPublicKeySize) { ServerPublicKey = new byte[MicroRatchetContext.ExpectedPublicKeySize]; } memory.Read(ServerPublicKey, 0, MicroRatchetContext.ExpectedPublicKeySize); } if (hasRatchet) { ReadRatchet(memory, kexFac); } Log.Verbose($"Read {memory.Position} bytes of client state"); }
public static EcdhRatchetStep[] InitializeClient(IKeyDerivation kdf, IDigest digest, byte[] rootKey, ArraySegment <byte> remotePublicKey0, ArraySegment <byte> remotePublicKey1, IKeyAgreement keyPair, byte[] receiveHeaderKey, byte[] sendHeaderKey, IKeyAgreement nextKeyPair) { if (receiveHeaderKey.Length != 32 || sendHeaderKey.Length != 32) { throw new InvalidOperationException("Keys need to be 32 bytes."); } Log.Verbose($"--Initialize ECDH Ratchet CLIENT"); Log.Verbose($"Root Key: {Log.ShowBytes(rootKey)}"); Log.Verbose($"ECDH Public 0: {Log.ShowBytes(remotePublicKey0)}"); Log.Verbose($"ECDH Public 1: {Log.ShowBytes(remotePublicKey1)}"); Log.Verbose($"ECDH Private: ({Log.ShowBytes(keyPair.GetPublicKey())})"); var e0 = new EcdhRatchetStep { EcdhKey = keyPair, SendHeaderKey = sendHeaderKey }; // receive chain doesn't exist Log.Verbose(" --Receiving Chain"); // send chain Log.Verbose(" --Sending Chain"); var scderived = keyPair.DeriveKey(remotePublicKey0); scderived = digest.ComputeDigest(scderived); Log.Verbose($" C Input Key: {Log.ShowBytes(rootKey)}"); Log.Verbose($" C Key Info: {Log.ShowBytes(scderived)}"); var sckeys = kdf.GenerateKeys(scderived, rootKey, 3, 32); Log.Verbose($" C Key Out 0: {Log.ShowBytes(sckeys[0])}"); Log.Verbose($" C Key Out 1: {Log.ShowBytes(sckeys[1])}"); Log.Verbose($" C Key Out 2: {Log.ShowBytes(sckeys[2])}"); rootKey = sckeys[0]; e0.SendingChain.Initialize(sckeys[1]); var nextSendHeaderKey = sckeys[2]; var e1 = InitializeServer(kdf, digest, keyPair, rootKey, remotePublicKey1, nextKeyPair, receiveHeaderKey, nextSendHeaderKey); return(new[] { e0, e1 }); }
public static EcdhRatchetStep Create(IKeyAgreement EcdhKey, byte[] NextRootKey, int receivingGeneration, byte[] receivingHeaderKey, byte[] receivingNextHeaderKey, byte[] receivingChainKey, int sendingGeneration, byte[] sendingHeaderKey, byte[] sendingNextHeaderKey, byte[] sendingChainKey) { if (NextRootKey != null && NextRootKey.Length != 32) { throw new InvalidOperationException("The next root key size needs to be 32 bytes"); } if (receivingHeaderKey != null && receivingHeaderKey.Length != 32) { throw new InvalidOperationException("The receiving header key size needs to be 32 bytes"); } if (receivingNextHeaderKey != null && receivingNextHeaderKey.Length != 32) { throw new InvalidOperationException("The next receiving header key size needs to be 32 bytes"); } if (receivingChainKey != null && receivingChainKey.Length != 32) { throw new InvalidOperationException("The receiving chain key size needs to be 32 bytes"); } if (sendingHeaderKey != null && sendingHeaderKey.Length != 32) { throw new InvalidOperationException("The sending header key size needs to be 32 bytes"); } if (sendingNextHeaderKey != null && sendingNextHeaderKey.Length != 32) { throw new InvalidOperationException("The next sending header key size needs to be 32 bytes"); } if (sendingChainKey != null && sendingChainKey.Length != 32) { throw new InvalidOperationException("The sending chain key size needs to be 32 bytes"); } var step = new EcdhRatchetStep() { EcdhKey = EcdhKey, NextRootKey = NextRootKey, ReceiveHeaderKey = receivingHeaderKey, NextReceiveHeaderKey = receivingNextHeaderKey, SendHeaderKey = sendingHeaderKey, NextSendHeaderKey = sendingNextHeaderKey }; step.ReceivingChain.Initialize(receivingChainKey); step.ReceivingChain.Generation = receivingGeneration; step.SendingChain.Initialize(sendingChainKey); step.SendingChain.Generation = sendingGeneration; return(step); }
internal static KrbDiffieHellmanDomainParameters FromKeyAgreement(IKeyAgreement agreement) { if (!(agreement.PublicKey is DiffieHellmanKey pk)) { throw new ArgumentException("Not a DH key agreement"); } return(new KrbDiffieHellmanDomainParameters { P = Pad(pk.Modulus), G = DepadRight(pk.Generator), Q = pk.Factor }); }
public EcdhRatchetStep Ratchet(IKeyDerivation kdf, IDigest digest, ArraySegment <byte> remotePublicKey, IKeyAgreement keyPair) { var nextStep = InitializeServer(kdf, digest, EcdhKey, NextRootKey, remotePublicKey, keyPair, NextReceiveHeaderKey, NextSendHeaderKey); NextRootKey = null; EcdhKey = null; NextSendHeaderKey = null; NextReceiveHeaderKey = null; return(nextStep); }
private static void AssertKeysAgree(IKeyAgreement alice, IKeyAgreement bob) { var aliceDerived = alice.GenerateAgreement(); var bobDerived = bob.GenerateAgreement(); var match = aliceDerived.Span.SequenceEqual(bobDerived.Span); if (!match) { Hex.Debug(aliceDerived.ToArray()); Hex.Debug(bobDerived.ToArray()); } Assert.IsTrue(match); var empty = new byte[aliceDerived.Length]; Assert.IsFalse(aliceDerived.Span.SequenceEqual(empty)); }
protected override void Dispose(bool disposing) { if (disposing) { ClientInitializationNonce?.Shred(); ClientInitializationNonce = null; RootKey?.Shred(); RootKey = null; FirstSendHeaderKey?.Shred(); FirstSendHeaderKey = null; FirstReceiveHeaderKey?.Shred(); FirstReceiveHeaderKey = null; ClientPublicKey?.Shred(); ClientPublicKey = null; (LocalEcdhRatchetStep0 as IDisposable)?.Dispose(); LocalEcdhRatchetStep0 = null; (LocalEcdhRatchetStep1 as IDisposable)?.Dispose(); LocalEcdhRatchetStep1 = null; } }
private void LoadInternal(Stream memory, IKeyAgreementFactory kexFac) { var versionInt = memory.ReadByte(); if (versionInt < 0) { throw new EndOfStreamException(); } var versionByte = (byte)versionInt; bool isClient = (versionByte & 0b0000_1000) != 0; bool hasInit = (versionByte & 0b0001_0000) != 0; bool hasRatchet = (versionByte & 0b0010_0000) != 0; bool hasEcdh = (versionByte & 0b0100_0000) != 0; bool hasClientPublicKey = (versionByte & 0b1000_0000) != 0; if (isClient) { throw new InvalidOperationException("The provided state is not server state"); } if (hasInit) { if (ClientInitializationNonce == null || ClientInitializationNonce.Length != MicroRatchetContext.InitializationNonceSize) { ClientInitializationNonce = new byte[MicroRatchetContext.InitializationNonceSize]; } if (RootKey == null || RootKey.Length != KeySizeInBytes) { RootKey = new byte[KeySizeInBytes]; } if (FirstSendHeaderKey == null || FirstSendHeaderKey.Length != KeySizeInBytes) { FirstSendHeaderKey = new byte[KeySizeInBytes]; } if (FirstReceiveHeaderKey == null || FirstReceiveHeaderKey.Length != KeySizeInBytes) { FirstReceiveHeaderKey = new byte[KeySizeInBytes]; } if (NextInitializationNonce == null || NextInitializationNonce.Length != MicroRatchetContext.InitializationNonceSize) { NextInitializationNonce = new byte[MicroRatchetContext.InitializationNonceSize]; } memory.Read(ClientInitializationNonce, 0, MicroRatchetContext.InitializationNonceSize); memory.Read(RootKey, 0, KeySizeInBytes); memory.Read(FirstSendHeaderKey, 0, KeySizeInBytes); memory.Read(FirstReceiveHeaderKey, 0, KeySizeInBytes); memory.Read(NextInitializationNonce, 0, MicroRatchetContext.InitializationNonceSize); } if (hasEcdh) { LocalEcdhRatchetStep0 = kexFac.Deserialize(memory); LocalEcdhRatchetStep1 = kexFac.Deserialize(memory); } if (hasClientPublicKey) { if (ClientPublicKey == null || ClientPublicKey.Length != KeySizeInBytes) { ClientPublicKey = new byte[32]; } memory.Read(ClientPublicKey, 0, 32); } if (hasRatchet) { ReadRatchet(memory, kexFac); } Log.Verbose($"Read {memory.Position} bytes of server state"); }
/// <summary> /// If overridden this method will cache the key agreement private keys to reduce key generation time. /// Note that caching Key Agreement private keys is not recommended as these keys should be ephemeral. /// </summary> /// <param name="agreement">The agreement private key to cache.</param> /// <returns>Returns true if the key was cached, otherwise it will return false.</returns> protected virtual bool CacheKeyAgreementParameters(IKeyAgreement agreement) => false;
/// <summary> /// Applies credential-specific changes to the KDC-REQ message and is what supplies the PKINIT properties to the request. /// </summary> /// <param name="req">The <see cref="KrbKdcReq"/> that will be modified.</param> public override void TransformKdcReq(KrbKdcReq req) { if (req == null) { throw new ArgumentNullException(nameof(req)); } this.agreement = this.StartKeyAgreement(); // We don't support the straight RSA mode because // it doesn't rely on ephemeral key agreement // which isn't great security-wise if (this.agreement == null) { throw OnlyKeyAgreementSupportedException(); } var padata = req.PaData.ToList(); KrbAuthPack authPack; if (this.SupportsEllipticCurveDiffieHellman) { authPack = this.CreateEllipticCurveDiffieHellmanAuthPack(req.Body); } else if (this.SupportsDiffieHellman) { authPack = this.CreateDiffieHellmanAuthPack(req.Body); } else { throw OnlyKeyAgreementSupportedException(); } Now(out DateTimeOffset ctime, out int usec); authPack.PKAuthenticator.CTime = ctime; authPack.PKAuthenticator.CuSec = usec; SignedCms signed = new SignedCms( new ContentInfo( IdPkInitAuthData, authPack.Encode().ToArray() ) ); var signer = new CmsSigner(this.Certificate) { IncludeOption = this.IncludeOption }; signed.ComputeSignature(signer, silent: !CanPrompt); var pk = new KrbPaPkAsReq { SignedAuthPack = signed.Encode() }; padata.Add(new KrbPaData { Type = PaDataType.PA_PK_AS_REQ, Value = pk.Encode() }); req.PaData = padata.ToArray(); }
protected override bool CacheKeyAgreementParameters(IKeyAgreement agreement) { return(false); }
private byte[] DeconstructMessage(State state, byte[] payload, byte[] headerKey, EcdhRatchetStep ratchetUsed, bool usedNextHeaderKey) { if (state == null) { throw new ArgumentNullException(nameof(state)); } if (payload == null) { throw new ArgumentNullException(nameof(payload)); } if (headerKey == null) { throw new ArgumentNullException(nameof(headerKey)); } var messageSize = payload.Length; var encryptedNonce = new ArraySegment <byte>(payload, 0, NonceSize); // decrypt the nonce var headerEncryptionNonce = new ArraySegment <byte>(payload, payload.Length - MacSize - HeaderIVSize, HeaderIVSize); var hcipher = new AesCtrMode(GetHeaderKeyCipher(headerKey), headerEncryptionNonce); var decryptedNonce = hcipher.Process(encryptedNonce); CheckNonce(headerEncryptionNonce); // get the ecdh bit var hasEcdh = (decryptedNonce[0] & 0b1000_0000) != 0; decryptedNonce[0] &= 0b0111_1111; // extract ecdh if needed var step = BigEndianBitConverter.ToInt32(decryptedNonce); if (hasEcdh) { var clientEcdhPublic = new ArraySegment <byte>(hcipher.Process(new ArraySegment <byte>(payload, NonceSize, EcNumSize))); if (ratchetUsed == null) { // an override header key was used. // this means we have to initialize the ratchet if (!(state is ServerState serverState)) { throw new InvalidOperationException("Only the server can initialize a ratchet."); } ratchetUsed = EcdhRatchetStep.InitializeServer(KeyDerivation, Digest, serverState.LocalEcdhRatchetStep0, serverState.RootKey, clientEcdhPublic, serverState.LocalEcdhRatchetStep1, serverState.FirstReceiveHeaderKey, serverState.FirstSendHeaderKey); serverState.Ratchets.Add(ratchetUsed); } else { if (usedNextHeaderKey) { // perform ecdh ratchet IKeyAgreement newEcdh = KeyAgreementFactory.GenerateNew(); // this is the hottest line in the deconstruct process: EcdhRatchetStep newRatchet = ratchetUsed.Ratchet(KeyDerivation, Digest, clientEcdhPublic, newEcdh); state.Ratchets.Add(newRatchet); ratchetUsed = newRatchet; } } } // get the inner payload key from the receive chain if (ratchetUsed == null) { throw new InvalidOperationException("An override header key was used but the message did not contain ECDH parameters"); } (var key, var _) = ratchetUsed.ReceivingChain.RatchetForReceiving(KeyDerivation, step); CheckNonce(key); // get the encrypted payload var payloadOffset = hasEcdh ? NonceSize + EcNumSize : NonceSize; var encryptedPayload = new ArraySegment <byte>(payload, payloadOffset, messageSize - payloadOffset - MacSize); // decrypt the inner payload var icipher = new AesCtrMode(AesFactory.GetAes(true, key), decryptedNonce); var decryptedInnerPayload = icipher.Process(encryptedPayload); return(decryptedInnerPayload); }
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; }
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); }