code wiki / (root) / nx_tls13_server_session_derive_hs.nx

nx_tls13_server_session_derive_hs.nx source

↩ module page · 144 lines · 5540 B

1// nx_tls13_server_session_derive_hs.nx -- 4th stone of the TLS 1.3 2// server handshake arc. 3// 4// After emit_sh, both sides compute the handshake-phase keying 5// material from the X25519 ECDHE shared secret + HKDF chain. Per 6// RFC 8446 ยง7.1: 7// 8// Early-Secret = HKDF-Extract(0, IKM=PSK_or_0) 9// empty_derived = Derive-Secret(Early-Secret, "derived", "") 10// Handshake-Secret = HKDF-Extract(salt=empty_derived, IKM=ECDHE) 11// 12// This commit ships handshake_secret derivation. Traffic secrets 13// (client_hs_traffic_secret / server_hs_traffic_secret) + traffic 14// keys (key + iv) ship in the next stones -- each composes a 15// Derive-Secret + HKDF-Expand-Label step against this commit's 16// handshake_secret. 17// 18// State machine: requires SH_SENT; advances state to EE_SENT 19// (NOTE: the message isn't actually emitted yet; the state 20// signifies "ready to emit EE because keys are derived"). v2 21// will introduce an HS_DERIVED intermediate state when the full 22// emit_ee_cert arc lands. 23// 24// license_tier: ORIGINAL 25 26import "nx_syscalls.nx" 27import "nx_x25519_ephemeral.nx" 28import "nx_hkdf.nx" 29import "nx_tls13.nx" 30import "nx_tls13_kdf.nx" 31import "nx_tls13_transcript.nx" 32import "nx_tls13_server_session.nx" 33import "nx_p256_ecdh.nx" 34 35const NX_TLS13_HS_HASH_LEN_SHA256: i64 = 32 36 37// Returns NX_TLS13_SSESSION_OK on success. 38 39func nx_tls13_server_session_derive_hs_secrets( 40 session: *Tls13ServerSession 41) -> i64 { 42 if (session as i64) == 0 { return NX_TLS13_SSESSION_BAD_STATE } 43 if session.state != NX_TLS13_SSTATE_SH_SENT { 44 return NX_TLS13_SSESSION_BAD_STATE 45 } 46 // Step 1: ECDHE shared secret per the negotiated group (rung B4 47 // server side: kex_group 23 = secp256r1 fallback chosen by 48 // recv_ch when the client offered no x25519 share). 49 let ecdhe: *u8 = sys_mmap(32) 50 if session.kex_group == 23 { 51 if (session.client_p256_pub as i64) == 0 { 52 return NX_TLS13_SSESSION_PROTOCOL_ERR 53 } 54 // p256_ecdh_shared validates format + on-curve (boundary: 55 // the share arrived off the wire). 56 let pcv: i64 = p256_ecdh_shared(session.p256_priv, 57 session.client_p256_pub, 65, 58 ecdhe) 59 if pcv == NX_P256_ECDH_BAD_POINT { return NX_TLS13_SSESSION_PROTOCOL_ERR } 60 if pcv != NX_P256_ECDH_OK { return NX_TLS13_SSESSION_INTERNAL } 61 } 62 if session.kex_group != 23 { 63 if (session.client_x25519_pub as i64) == 0 { 64 return NX_TLS13_SSESSION_PROTOCOL_ERR 65 } 66 let ecv: i64 = x25519_shared_secret(session.x25519_priv, 67 session.client_x25519_pub, 68 ecdhe) 69 if ecv != 0 { return NX_TLS13_SSESSION_INTERNAL } 70 } 71 72 // Step 2: Early-Secret = HKDF-Extract(salt=0, IKM=32 zero bytes) 73 // No PSK in our v1 server, so IKM is all zeros. 74 let zero_salt: *u8 = sys_mmap(32) 75 let zero_ikm: *u8 = sys_mmap(32) 76 // sys_mmap returns zero-initialised memory; no explicit zero needed. 77 let early_secret: *u8 = sys_mmap(32) 78 let r1: i64 = hkdf_extract(zero_salt, 32, zero_ikm, 32, early_secret) 79 if r1 != 0 { return NX_TLS13_SSESSION_INTERNAL } 80 81 // Step 3: empty_derived = Derive-Secret(early_secret, "derived", "") 82 // Derive-Secret(s, l, "") = HKDF-Expand-Label(s, l, hash(""), hash_len) 83 // where hash("") for SHA-256 is the well-known 32-byte digest: 84 // e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 85 let empty_hash: *u8 = sys_mmap(32) 86 empty_hash[0] = 0xe3 as u8 87 empty_hash[1] = 0xb0 as u8 88 empty_hash[2] = 0xc4 as u8 89 empty_hash[3] = 0x42 as u8 90 empty_hash[4] = 0x98 as u8 91 empty_hash[5] = 0xfc as u8 92 empty_hash[6] = 0x1c as u8 93 empty_hash[7] = 0x14 as u8 94 empty_hash[8] = 0x9a as u8 95 empty_hash[9] = 0xfb as u8 96 empty_hash[10] = 0xf4 as u8 97 empty_hash[11] = 0xc8 as u8 98 empty_hash[12] = 0x99 as u8 99 empty_hash[13] = 0x6f as u8 100 empty_hash[14] = 0xb9 as u8 101 empty_hash[15] = 0x24 as u8 102 empty_hash[16] = 0x27 as u8 103 empty_hash[17] = 0xae as u8 104 empty_hash[18] = 0x41 as u8 105 empty_hash[19] = 0xe4 as u8 106 empty_hash[20] = 0x64 as u8 107 empty_hash[21] = 0x9b as u8 108 empty_hash[22] = 0x93 as u8 109 empty_hash[23] = 0x4c as u8 110 empty_hash[24] = 0xa4 as u8 111 empty_hash[25] = 0x95 as u8 112 empty_hash[26] = 0x99 as u8 113 empty_hash[27] = 0x1b as u8 114 empty_hash[28] = 0x78 as u8 115 empty_hash[29] = 0x52 as u8 116 empty_hash[30] = 0xb8 as u8 117 empty_hash[31] = 0x55 as u8 118 119 let derived_label: *u8 = "derived" 120 let empty_derived: *u8 = sys_mmap(32) 121 let r2: i64 = tls13_hkdf_expand_label( 122 early_secret, 123 derived_label, 7, 124 empty_hash, 32, 125 32, 126 empty_derived) 127 if r2 != NX_TLS13_KDF_VERDICT_OK { 128 return NX_TLS13_SSESSION_INTERNAL 129 } 130 131 // Step 4: Handshake-Secret = HKDF-Extract(salt=empty_derived, IKM=ECDHE) 132 let handshake_secret: *u8 = sys_mmap(32) 133 let r3: i64 = hkdf_extract(empty_derived, 32, ecdhe, 32, handshake_secret) 134 if r3 != 0 { return NX_TLS13_SSESSION_INTERNAL } 135 136 session.handshake_secret = handshake_secret 137 138 // State advance: SH_SENT -> EE_SENT (placeholder; EE message 139 // not actually emitted yet -- the EE bytes are queued for the 140 // next stone and the state will be the precondition there). 141 session.state = NX_TLS13_SSTATE_EE_SENT 142 143 return NX_TLS13_SSESSION_OK 144}