Reputation: 141
I am trying to implement a DTLS server in nodejs. When I try to decrypt the EncryptedHandshakeMessage
from the client, I am getting the following error.
Error: Unsupported state or unable to authenticate data
What am I doing wrong? Any help would be much appreciated.
I am trying to implement a DTLS server in nodejs.
My cipher suite is, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b)
My server sends ServerKeyExchange
with the following params
EC Diffie-Hellman Server Params
Curve Type: named_curve (0x03)
Named Curve: secp256r1 (0x0017)
Pubkey Length: 65
Pubkey: 04581008311d54c64afad46c931c92911e8df9b0edc6dd1d9703ab678412cf5af53c11d8…
Signature Algorithm: ecdsa_secp256r1_sha256 (0x0403)
Signature Length: 72
Signature: 3046022100a986cc75f373f6400e7223c28e4c770acbe17ff6c85f09476c25948aa151c6…
I generate the public key and private key using the following code. Using prime256v1
since it represents the same as secp256r1
. RFC 4492, Appendix-A
const ecdh = crypto.createECDH('prime256v1');
ecdh.generateKeys();
const publicKey = ecdh.getPublicKey(null, 'uncompressed');
const privateKey = ecdh.getPrivateKey();
Now I have the following on the server.
clientRandom
- Got from ClientHello
serverRandom
- My server generated it. Sent it in ServerHello
.
serverPrivateKey
- Got from above block
serverPublicKey
- Got from above block. Sent in ServerKeyExchange
.
clientPublicKey
- Got from ClientKeyExchange
After this, I compute pre-master secret using the following code
const ecdh = crypto.createECDH('prime256v1');
ecdh.setPrivateKey(serverPrivateKey);
const preMasterSecret = ecdh.computeSecret(clientPublicKey);
Then compute master secret
const masterSecret = PRF(
preMasterSecret,
Buffer.from("master secret"),
Buffer.concat([clientRandom, serverRandom]),
48
);
Then compute the key block. Not computing MAC keys because RFC 5246, 6.2.3.3 says "No MAC key is used" for AEAD ciphers.
const seed = Buffer.concat([clientRandom, serverRandom]);
const length =
(2 * 16) + // client_write_key + server_write_key
(2 * 4); // client_write_IV + server_write_IV
const keyBlock = PRF(
masterSecret,
Buffer.from('key expansion'),
seed,
length
);
const keyLength = 16;
const ivLength = 4;
let start = 0, end = start + keyLength;
const clientWriteKey = keyBlock.slice(start, end);
start = end, end = start + keyLength;
const serverWriteKey = keyBlock.slice(start, end);
start = end, end = start + ivLength;
const clientWriteIV = keyBlock.slice(start, end);
start = end, end = start + ivLength;
const serverWriteIV = keyBlock.slice(start, end);
After I receive EncryptedHandshakeMessage from server, I extract the first 8 bytes from the payload to get the nonce_explicit
value as defined in RFC 5246, 6.2.3.3
I concat clientWriteIV
as nonce_implicit
(in that order) as stated in the same above link and generate nonce
.
const nonce = Buffer.concat([
clientWriteIV,
encryptedHandshakeMessage.slice(0, 8)
]);
Then, I construct the Additional Authenticated Data as per RFC 5246, 6.2.3.3. Since I use DTLS, seq_num is generated as per RFC 6347, 4.1.2.1.
const aad = [
// epoch
0x00, 0x01,
//sequence number
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// content type
0x16,
// version
0xfe, 0xfd,
// length (48 bytes)
0x00, 0x30
]
Then I performed the decryption operation as follows
const decipher = crypto.createDecipheriv('aes-128-gcm', clientWriteKey, nonce);
decipher.setAuthTag(encryptedHandshakeMessage.slice(-16));
decipher.setAAD(Buffer.from(aad));
const decrypted = decipher.update(encryptedHandshakeMessage.slice(0, -16), null, 'utf8');
decipher.final();
The line decipher.final()
is failing with the following error.
Error: Unsupported state or unable to authenticate data
I use OpenSSL s_client as my client in DTLS 1.2 mode. I had logged the keys using -keylogfile
argument and the output is as follows
CLIENT_RANDOM 6e0ed5aec5691fc199d1019ecfc1e4a8629c9ed4208dcad30e8005c82364099c d228852f0c13a0b3122065519c3d4b66b8c2674232686221f77c5cfb49001e99528be5900ea1d88a23c533f8c544c6d9
If my knowledge is correct, the second part indicates the master secret which does not match with master secret I computed in my server.
What am I doing wrong?
Full code for reference.
const crypto = require('crypto');
const clientRandom = Buffer.from("6e0ed5aec5691fc199d1019ecfc1e4a8629c9ed4208dcad30e8005c82364099c", 'hex')
const serverRandom = Buffer.from("9c27d9ef09af50cf0c9aba9e535714ed07f2f522ec672a4c17ff550604f950e4", 'hex')
const serverPrivateKey = Buffer.from("4ac2c6a823a455c19101b2b61d3fd6b4c923cb6512f7b1488a717a86c4b1e9c0", 'hex')
const serverPublicKey = Buffer.from("04581008311d54c64afad46c931c92911e8df9b0edc6dd1d9703ab678412cf5af53c11d81935b463e3da85f56dabb0aa998f3dd09ea3629a05cff3a0c801f7bdc7", 'hex')
const clientPublicKey = Buffer.from("04ad753ed35f557cf9419d7d03d51510b4c1fa282ddd0c432b43de1256cf561df16c7b355b17328f2f72ec9ce4361d7855d92e643904e501387b46a4edfe1c67d6", 'hex')
const encryptedHandshakeMessage = Buffer.from("713a474b4484511c7de96486fa9bc8d1716ed73717c42c0b56277ab6453d349770db77bb40353fb622c5c3422d79b981", 'hex')
const aad = [
// epoch
0x00, 0x01,
//sequence number
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// content type
0x16,
// version
0xfe, 0xfd,
// length (48 bytes)
0x00, 0x30
]
function PRF(secret, label, seed, length){
const labelSeedConcat = Buffer.concat([label, seed]);
function hmacHash(secret, seed){
const hmac = crypto.createHmac('sha256', Buffer.from(secret));
hmac.update(seed);
return hmac.digest();
}
function A(i){
if(i === 0){
return labelSeedConcat;
}
else{
return hmacHash(secret, A(i-1));
}
}
return Buffer.concat([
hmacHash(secret, Buffer.concat([A(1), labelSeedConcat])),
hmacHash(secret, Buffer.concat([A(2), labelSeedConcat])),
hmacHash(secret, Buffer.concat([A(3), labelSeedConcat])),
hmacHash(secret, Buffer.concat([A(4), labelSeedConcat])),
]).slice(0, length);
}
const ecdh = crypto.createECDH('prime256v1');
ecdh.setPrivateKey(serverPrivateKey);
const preMasterSecret = ecdh.computeSecret(clientPublicKey);
const masterSecret = PRF(
preMasterSecret,
Buffer.from("master secret"),
Buffer.concat([clientRandom, serverRandom]),
48
);
console.log('Master secret:', masterSecret.toString('hex'));
function computeSymmetricKeysFromMasterSecret(masterSecret, clientRandom, serverRandom){
const seed = Buffer.concat([clientRandom, serverRandom]);
const length =
(2 * 16) + // client_write_key + server_write_key
(2 * 4); // client_write_IV + server_write_IV
const keyBlock = PRF(
masterSecret,
Buffer.from('key expansion'),
seed,
length
);
const macKeyLength = 32;
const keyLength = 16;
const ivLength = 4;
let start = 0, end = 0;
start = end, end = start + keyLength;
const clientWriteKey = keyBlock.slice(start, end);
start = end, end = start + keyLength;
const serverWriteKey = keyBlock.slice(start, end);
start = end, end = start + ivLength;
const clientWriteIV = keyBlock.slice(start, end);
start = end, end = start + ivLength;
const serverWriteIV = keyBlock.slice(start, end);
return {clientWriteKey, serverWriteKey, clientWriteIV, serverWriteIV};
}
const {clientWriteKey, serverWriteKey, clientWriteIV, serverWriteIV} = computeSymmetricKeysFromMasterSecret(masterSecret, clientRandom, serverRandom);
/*
nonce = implicit nonce (4 bytes) + explicit nonce (8 bytes)
*/
const nonce = Buffer.concat([
clientWriteIV,
encryptedHandshakeMessage.slice(0, 8)
]);
const decipher = crypto.createDecipheriv('aes-128-gcm', clientWriteKey, nonce);
decipher.setAuthTag(encryptedHandshakeMessage.slice(-16));
decipher.setAAD(Buffer.from(aad));
const decrypted = decipher.update(encryptedHandshakeMessage.slice(0, -16), null, 'utf8');
decipher.final();
console.log('Decrypted:', decrypted);
Then I thought, during key block generation, maybe I should actually compute the MAC keys, but not use it? I tried that too. It did not work either.
const seed = Buffer.concat([clientRandom, serverRandom]);
const length =
(2 * 32) + // client_write_MAC_key + server_write_MAC_key
(2 * 16) + // client_write_key + server_write_key
(2 * 4); // client_write_IV + server_write_IV
const keyBlock = PRF(
masterSecret,
Buffer.from('key expansion'),
seed,
length
);
const macKeyLength = 32;
const keyLength = 16;
const ivLength = 4;
let start = 0, end = 0;
start = end, end = start + macKeyLength;
const clientWriteMACKey = keyBlock.slice(start, end);
start = end, end = start + macKeyLength;
const serverWriteMACKey = keyBlock.slice(start, end);
start = end, end = start + keyLength;
const clientWriteKey = keyBlock.slice(start, end);
start = end, end = start + keyLength;
const serverWriteKey = keyBlock.slice(start, end);
start = end, end = start + ivLength;
const clientWriteIV = keyBlock.slice(start, end);
start = end, end = start + ivLength;
const serverWriteIV = keyBlock.slice(start, end);
Edit:
Please find the wireshark dump of the handshake process here
I have a permanent certificate and key (pem files) for my server. This certificate is trusted by the client. I use this key file (corresponding to that certificate) to sign the serverPublicKey
value and send it via ServerKeyExchange. Am I doing this correct?
Upvotes: 1
Views: 97
Reputation: 141
I had done several mistakes which caused the failure in decryption. Will list them below.
master secret
is wrong. server_random
comes first and client_random
comes next.key_block = PRF(SecurityParameters.master_secret,
"key expansion",
SecurityParameters.server_random +
SecurityParameters.client_random);
Client Explicit nonce was included with the encrypted_block
input for decryption. It should not have been added.
I had negotiated extended master secret
extension RFC 7627. But not calculated the master secret according to that extension. So, removed the extension temporarily.
The length specified in Additional authenticated data should be TLSCompressed.length
. i.e, the length of payload BEFORE encryption. I used record length instead of fragment length.
Fixing all the above stated issues fixed my issue and I am now able to decrypt the payload.
Thanks to all the commenters and answerers who helped me.
Upvotes: 1
Reputation: 824
(Sorry to not be inline with the stackoverflow rules. Trying to fix encryption is unfortunately a step-by-step approach and each step may take pretty much information.)
ECDHE master secret 50D5CA5D741B75B33DE31CFBCBD2EC9E285EFEDFED25E23BECE0E41E334F973D5D3171DFE54FB0834C98889868F1CB3A
Keys Seed 9C27D9EF09AF50CF0C9ABA9E535714ED07F2F522EC672A4C17FF550604F950E46E0ED5AEC5691FC199D1019ECFC1E4A8629C9ED4208DCAD30E8005C82364099C
Request Bytes 40
Client Write Key @0,48B8AE63A327BC7041B95DD59F49A5CD
Server Write Key @16,51FB049F6088FB49B406D3B5597C2E45
Client Write IV @32,6F0ED63C
Server Write IV @36,14688C2E
ClientWriteKey: 4a59ee3e8a969ed84d5358ea103b250f | ClientWriteIV: a57214b5
Are that the calculated keys from your example data? If so, they differ.
Upvotes: 0
Reputation: 824
(It's more a comment, but using "comment" is limited.)
If you use openssl, e.g.
openssl s_client -dtls1_2 -connect californium.eclipseprojects.io:5684 -cipher ECDHE-ECDSA-AES128-GCM-SHA256
that prints
SSL-Session:
Protocol : DTLSv1.2
Cipher : ECDHE-ECDSA-AES128-GCM-SHA256
Session-ID: DF8ACBFEA82FF766892EDBEF6BDA283063FF029BE5978483A4EC4FAEBCB3B1E1
Session-ID-ctx:
Master-Key: C5FE3608B349CB4155FAD5E42B9BC7CCCD3132DE07D5FD4A878091AB62EEA1A5DBF243877178A2CBF4656B61F4F9066E
PSK identity: None
PSK identity hint: None
SRP username: None
Start Time: 1730107483
Timeout : 7200 (sec)
Verify return code: 20 (unable to get local issuer certificate)
Extended master secret: yes
Did you check your master key with the one from openssl?
And may be to check your calculations, here some values recorded from a handshake openssl - Eclipse/Californium:
Client Random C527E6426DB2AF87902869278AEF55D27445D7F78FEE725A63888B9F23E4F677
Server Random 671FC5EF196625F2BB155927089FC13A66FD50D47EF229507E8692DB8EC76E54
Seed C527E6426DB2AF87902869278AEF55D27445D7F78FEE725A63888B9F23E4F677671FC5EF196625F2BB155927089FC13A66FD50D47EF229507E8692DB8EC76E54
Client ECDHE Public Key 04CB9337C325BD800DA1F6C97F193A8DC1100F5B825AB1C2F59E850855337B13B9D4074D536391A7CBFD639DBB9CE13B73BF172947337AA40B59DF82502EECCE5D
Server ECDHE Private Key 7689F02181AB5C19CBE9671D4BC604F232E4381485EB82E752C64A9445E76B47
Premaster Secret 163CD5B46D90F4CF0B8268B3AB973E7D12ED4AF0D12F3FB46CECF6D62B42021F
Master Secret 20F8969E8FA0E229C0A315D58FBE4884E97B959D55F730EADEDC235D7B7DAC8C57F82B0E7355EBB59C55D4B4FD2D56A6
Maybe you check your calculations with these values?
If I use your values above, I get
Client Random 6e0ed5aec5691fc199d1019ecfc1e4a8629c9ed4208dcad30e8005c82364099c
Server Random 9c27d9ef09af50cf0c9aba9e535714ed07f2f522ec672a4c17ff550604f950e4
Client ECDHE Public Key 04ad753ed35f557cf9419d7d03d51510b4c1fa282ddd0c432b43de1256cf561df16c7b355b17328f2f72ec9ce4361d7855d92e643904e501387b46a4edfe1c67d6
Server ECDHE Private Key 4ac2c6a823a455c19101b2b61d3fd6b4c923cb6512f7b1488a717a86c4b1e9c0
Premaster Secret 4B59F97B1EBBB8144FCE2A3EC11BFDC9ADBDA67709415F12DE4EBEEDDC266B8C
Master Secret 50D5CA5D741B75B33DE31CFBCBD2EC9E285EFEDFED25E23BECE0E41E334F973D5D3171DFE54FB0834C98889868F1CB3A
But that doesn't match your Master Key in keylog with
d228852f0c13a0b3122065519c3d4b66b8c2674232686221f77c5cfb49001e99528be5900ea1d88a23c533f8c544c6d9
Upvotes: 0
Reputation: 824
A handshake requires quite a lot of calculations and the nasty point of cryptography is, that a small mistake has an large effort and is hard to track back.
I tried to check your calculations, and one issue may be the used seed for the PRF. To generate the master secret, the used seed
Buffer.concat([clientRandom, serverRandom]),
is well, but for the key-expansion, it's the inverted order,
const seed = Buffer.concat([clientRandom, serverRandom]);
must be
const seed = Buffer.concat([serverRandom, clientRandom]);
see RFC5246, 6.3 Key Calculations
key_block = PRF(SecurityParameters.master_secret,
"key expansion",
SecurityParameters.server_random +
SecurityParameters.client_random);
The order here is swapped compared to the order in calculating the master secret see RFC 5246, 8.1, Computing the Master Secret
master_secret = PRF(pre_master_secret, "master secret",
ClientHello.random + ServerHello.random)
[0..47];
Please: note, that may be one issue, but may be not the only one!
Upvotes: 2