|
using System; |
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using System.Text; |
|
using System.Security.Cryptography; |
|
|
|
namespace Konamiman.NestorTls.TlsClient.Cryptography; |
|
|
|
/* |
|
* This class holds the keys and IVs for a TLS 1.3 connection, |
|
* and provides methods to (re)calculate them. |
|
*/ |
|
internal class TrafficKeys |
|
{ |
|
readonly HMAC hmac; |
|
readonly HashAlgorithm hash; |
|
readonly int hashSize; |
|
readonly int keyLength; |
|
readonly int ivLength; |
|
static readonly byte[] empty = []; |
|
byte[] handshakeSecret = null; |
|
readonly byte[] emptyHash; |
|
|
|
byte[] clientSecret; |
|
byte[] serverSecret; |
|
public byte[] ClientKey { get; private set; } |
|
public byte[] ServerKey { get; private set; } |
|
public byte[] ClientIv { get; private set; } |
|
public byte[] ServerIv { get; private set; } |
|
|
|
public TrafficKeys(HMAC hmacAlgorithm, HashAlgorithm hashAlgorithgm, int keyLength, int ivLength) |
|
{ |
|
this.hmac = hmacAlgorithm; |
|
this.hash = hashAlgorithgm; |
|
hashSize = hmacAlgorithm.HashSize / 8; //We get bits, we need bytes |
|
this.keyLength = keyLength; |
|
this.ivLength = ivLength; |
|
emptyHash = hash.ComputeHash(empty); |
|
} |
|
|
|
/* |
|
* Compute the handshake traffic secrets according to RFC8446, section 7.1 |
|
* (https://datatracker.ietf.org/doc/html/rfc8446#section-7.1) |
|
*/ |
|
public void ComputeHandshakeKeys(byte[] sharedSecret, byte[] handshakeHash) |
|
{ |
|
var earlySecret = Extract(empty, Enumerable.Repeat<byte>(0, hashSize).ToArray()); |
|
var derivedSecret = ExpandLabel(earlySecret, "derived", emptyHash, hashSize); |
|
handshakeSecret = Extract(derivedSecret, sharedSecret); |
|
|
|
clientSecret = ExpandLabel(handshakeSecret, "c hs traffic", handshakeHash, hashSize); |
|
serverSecret = ExpandLabel(handshakeSecret, "s hs traffic", handshakeHash, hashSize); |
|
ComputeKeysFromSecrets(); |
|
} |
|
|
|
/* |
|
* Compute the application traffic secrets according to RFC8446, section 7.1 |
|
* (https://datatracker.ietf.org/doc/html/rfc8446#section-7.1) |
|
*/ |
|
public void ComputeApplicationKeys(byte[] handshakeHash) |
|
{ |
|
if(handshakeSecret is null) { |
|
throw new InvalidOperationException($"{nameof(ComputeApplicationKeys)} can't be invoked before ${nameof(ComputeHandshakeKeys)}"); |
|
} |
|
|
|
var derivedSecret = ExpandLabel(handshakeSecret, "derived", emptyHash, hashSize); |
|
var masterSecret = Extract(derivedSecret, Enumerable.Repeat<byte>(0, hashSize).ToArray()); |
|
|
|
clientSecret = ExpandLabel(masterSecret, "c ap traffic", handshakeHash, hashSize); |
|
serverSecret = ExpandLabel(masterSecret, "s ap traffic", handshakeHash, hashSize); |
|
ComputeKeysFromSecrets(); |
|
} |
|
|
|
/* |
|
* Update the client application traffic keys according to RFC8446, section 7.2 |
|
* (https://datatracker.ietf.org/doc/html/rfc8446#section-7.2) |
|
*/ |
|
public void UpdateClientKeys() |
|
{ |
|
clientSecret = ExpandLabel(clientSecret, "traffic upd", empty, hashSize); |
|
ComputeKeysFromSecrets(forServer: false); |
|
} |
|
|
|
/* |
|
* Update the server application traffic keys according to RFC8446, section 7.2 |
|
* (https://datatracker.ietf.org/doc/html/rfc8446#section-7.2) |
|
*/ |
|
public void UpdateServerKeys() |
|
{ |
|
serverSecret = ExpandLabel(serverSecret, "traffic upd", empty, hashSize); |
|
ComputeKeysFromSecrets(forClient: false); |
|
} |
|
|
|
/* |
|
* Derive the handshake or application traffic keys according to RFC8446, section 7.3 |
|
* (https://datatracker.ietf.org/doc/html/rfc8446#section-7.3) |
|
*/ |
|
private void ComputeKeysFromSecrets(bool forClient = true, bool forServer = true) |
|
{ |
|
if(forClient) { |
|
ClientKey = ExpandLabel(clientSecret, "key", empty, keyLength); |
|
ClientIv = ExpandLabel(clientSecret, "iv", empty, ivLength); |
|
} |
|
|
|
if(forServer) { |
|
ServerKey = ExpandLabel(serverSecret, "key", empty, keyLength); |
|
ServerIv = ExpandLabel(serverSecret, "iv", empty, ivLength); |
|
} |
|
} |
|
|
|
/* |
|
* "Extract" function as per RFC5869, section 2.2 |
|
* (https://datatracker.ietf.org/doc/html/rfc5869#section-2.2) |
|
*/ |
|
private byte[] Extract(byte[] salt, byte[] ikm) |
|
{ |
|
hmac.Key = salt; |
|
return hmac.ComputeHash(ikm); |
|
} |
|
|
|
/* |
|
* "Expand" function as per RFC5869, section 2.3 |
|
* (https://datatracker.ietf.org/doc/html/rfc5869#section-2.3) |
|
*/ |
|
private byte[] Expand(byte[] prk, byte[] info, int length) |
|
{ |
|
hmac.Key = prk; |
|
var steps = (int)Math.Ceiling((decimal)length / hashSize); |
|
var result = new List<byte>(); |
|
var singleByte = new byte[] { 1 }; |
|
var previous = empty; |
|
|
|
for(var i = 0; i < steps; i++) { |
|
previous = hmac.ComputeHash(previous.Concat(info).Concat(singleByte).ToArray()); |
|
result.AddRange(previous); |
|
singleByte[0]++; |
|
} |
|
|
|
return result.Take(length).ToArray(); |
|
} |
|
|
|
/* |
|
* "Expand label" function as per RFC8446, section 7.1 |
|
* (https://datatracker.ietf.org/doc/html/rfc8446#section-7.1) |
|
*/ |
|
byte[] ExpandLabel(byte[] secret, string label, byte[] context, int length) |
|
{ |
|
/* |
|
struct { |
|
uint16 length = Length; |
|
opaque label<7..255> = "tls13 " + Label; |
|
opaque context<0..255> = Context; |
|
} HkdfLabel; |
|
*/ |
|
|
|
var labelBytes = Encoding.ASCII.GetBytes("tls13 " + label); |
|
var hkdfLabel = new byte[] { |
|
(byte)(length >> 8), (byte)(length & 0xFF), //fixed-size "uint16 length" |
|
(byte)labelBytes.Length, //size indicator for "label<7..255>" |
|
}.Concat(labelBytes) |
|
.Concat(new byte[] { (byte)context.Length }) //size indicator for "context<0..255>" |
|
.Concat(context) |
|
.ToArray(); |
|
|
|
return Expand(secret, hkdfLabel, length).Take(length).ToArray(); |
|
} |
|
} |