1#![forbid(unsafe_code)]
2
3use super::*;
4use crate::wire::ProtocolLimits;
5
6pub fn append_auth_phase_one(
7 out: &mut Vec<u8>,
8 user: &str,
9 program: &str,
10 machine: &str,
11 osuser: &str,
12 terminal: &str,
13 pid: u32,
14) -> Result<()> {
15 let mut writer = TtcWriter::new();
16 writer.write_function_code(TNS_FUNC_AUTH_PHASE_ONE);
17 write_auth_header(&mut writer, user, TNS_AUTH_MODE_LOGON, 5)?;
18 write_key_value(&mut writer, "AUTH_TERMINAL", terminal, 0)?;
19 write_key_value(&mut writer, "AUTH_PROGRAM_NM", program, 0)?;
20 write_key_value(&mut writer, "AUTH_MACHINE", machine, 0)?;
21 write_key_value(&mut writer, "AUTH_PID", &pid.to_string(), 0)?;
22 write_key_value(&mut writer, "AUTH_SID", osuser, 0)?;
23 out.extend_from_slice(&writer.into_bytes());
24 Ok(())
25}
26
27#[derive(Clone, Copy, Debug)]
37pub struct TokenPop<'a> {
38 pub header: &'a str,
39 pub signature: &'a str,
40}
41
42pub fn append_auth_phase_two_token(
52 out: &mut Vec<u8>,
53 user: &str,
54 token: &str,
55 driver_name: &str,
56 version_num: u32,
57 connect_string: &str,
58 edition: Option<&str>,
59) -> Result<()> {
60 append_auth_phase_two_token_with_pop(
61 out,
62 user,
63 token,
64 driver_name,
65 version_num,
66 connect_string,
67 edition,
68 None,
69 )
70}
71
72#[allow(clippy::too_many_arguments)] pub fn append_auth_phase_two_token_with_pop(
80 out: &mut Vec<u8>,
81 user: &str,
82 token: &str,
83 driver_name: &str,
84 version_num: u32,
85 connect_string: &str,
86 edition: Option<&str>,
87 pop: Option<TokenPop<'_>>,
88) -> Result<()> {
89 let mut writer = TtcWriter::new();
90 writer.write_function_code(TNS_FUNC_AUTH_PHASE_TWO);
91 let mut num_pairs = 5u32;
95 let auth_mode = if pop.is_some() {
96 num_pairs += 2;
98 TNS_AUTH_MODE_LOGON | TNS_AUTH_MODE_IAM_TOKEN
99 } else {
100 TNS_AUTH_MODE_LOGON
101 };
102 if edition.is_some() {
103 num_pairs += 1;
104 }
105 if !connect_string.is_empty() {
106 num_pairs += 1;
107 }
108 write_auth_header(&mut writer, user, auth_mode, num_pairs)?;
109 write_key_value(&mut writer, "AUTH_TOKEN", token, 0)?;
110 write_key_value(&mut writer, "SESSION_CLIENT_CHARSET", "873", 0)?;
111 write_key_value(&mut writer, "SESSION_CLIENT_DRIVER_NAME", driver_name, 0)?;
112 write_key_value(
113 &mut writer,
114 "SESSION_CLIENT_VERSION",
115 &version_num.to_string(),
116 0,
117 )?;
118 write_key_value(
119 &mut writer,
120 "AUTH_ALTER_SESSION",
121 "ALTER SESSION SET TIME_ZONE='+00:00'\0",
122 1,
123 )?;
124 if let Some(pop) = pop {
128 write_key_value(&mut writer, "AUTH_HEADER", pop.header, 0)?;
129 write_key_value(&mut writer, "AUTH_SIGNATURE", pop.signature, 0)?;
130 }
131 if let Some(edition) = edition {
136 write_key_value(&mut writer, "AUTH_ORA_EDITION", edition, 0)?;
137 }
138 if !connect_string.is_empty() {
139 write_key_value(&mut writer, "AUTH_CONNECT_STRING", connect_string, 0)?;
140 }
141 out.extend_from_slice(&writer.into_bytes());
142 Ok(())
143}
144
145pub fn build_auth_phase_two_payload(
146 user: &str,
147 encrypted: &crate::crypto::EncryptedPassword,
148 driver_name: &str,
149 version_num: u32,
150 connect_string: &str,
151) -> Result<Vec<u8>> {
152 build_auth_phase_two_payload_with_seq(
153 user,
154 encrypted,
155 driver_name,
156 version_num,
157 connect_string,
158 1,
159 )
160}
161
162pub fn build_auth_phase_two_payload_with_seq(
163 user: &str,
164 encrypted: &crate::crypto::EncryptedPassword,
165 driver_name: &str,
166 version_num: u32,
167 connect_string: &str,
168 seq_num: u8,
169) -> Result<Vec<u8>> {
170 build_auth_phase_two_payload_with_context_with_seq(
171 user,
172 encrypted,
173 driver_name,
174 version_num,
175 connect_string,
176 seq_num,
177 &[],
178 )
179}
180
181pub fn build_auth_phase_two_payload_with_context_with_seq(
182 user: &str,
183 encrypted: &crate::crypto::EncryptedPassword,
184 driver_name: &str,
185 version_num: u32,
186 connect_string: &str,
187 seq_num: u8,
188 app_context: &[(String, String, String)],
189) -> Result<Vec<u8>> {
190 build_auth_phase_two_payload_with_proxy_with_seq(
191 user,
192 encrypted,
193 driver_name,
194 version_num,
195 connect_string,
196 seq_num,
197 app_context,
198 None,
199 None,
200 ClientCapabilities::default().ttc_field_version,
201 )
202}
203
204#[allow(clippy::too_many_arguments)]
214pub fn build_auth_phase_two_payload_with_proxy_with_seq(
215 user: &str,
216 encrypted: &crate::crypto::EncryptedPassword,
217 driver_name: &str,
218 version_num: u32,
219 connect_string: &str,
220 seq_num: u8,
221 app_context: &[(String, String, String)],
222 proxy_user: Option<&str>,
223 edition: Option<&str>,
224 ttc_field_version: u8,
225) -> Result<Vec<u8>> {
226 let mut writer = TtcWriter::new();
227 writer.write_function_code_with_seq(TNS_FUNC_AUTH_PHASE_TWO, seq_num);
228 if version_gates::writes_pipeline_token(ttc_field_version) {
229 writer.write_ub8(0);
230 }
231 let mut num_pairs = 6u32;
232 if encrypted.speedy_key.is_some() {
233 num_pairs += 1;
234 }
235 if proxy_user.is_some() {
236 num_pairs += 1;
237 }
238 if !connect_string.is_empty() {
239 num_pairs += 1;
240 }
241 if edition.is_some() {
242 num_pairs += 1;
243 }
244 let app_context_pairs =
245 app_context
246 .len()
247 .checked_mul(3)
248 .ok_or(ProtocolError::InvalidPacketLength {
249 length: app_context.len(),
250 minimum: 0,
251 })?;
252 num_pairs +=
253 u32::try_from(app_context_pairs).map_err(|_| ProtocolError::InvalidPacketLength {
254 length: app_context.len(),
255 minimum: 0,
256 })?;
257 write_auth_header(
258 &mut writer,
259 user,
260 TNS_AUTH_MODE_LOGON | TNS_AUTH_MODE_WITH_PASSWORD,
261 num_pairs,
262 )?;
263 if let Some(proxy_user) = proxy_user {
264 write_key_value(&mut writer, "PROXY_CLIENT_NAME", proxy_user, 0)?;
265 }
266 write_key_value(&mut writer, "AUTH_SESSKEY", &encrypted.session_key, 1)?;
267 if let Some(speedy_key) = &encrypted.speedy_key {
268 write_key_value(&mut writer, "AUTH_PBKDF2_SPEEDY_KEY", speedy_key, 0)?;
269 }
270 write_key_value(&mut writer, "AUTH_PASSWORD", &encrypted.password, 0)?;
271 write_key_value(&mut writer, "SESSION_CLIENT_CHARSET", "873", 0)?;
272 write_key_value(&mut writer, "SESSION_CLIENT_DRIVER_NAME", driver_name, 0)?;
273 write_key_value(
274 &mut writer,
275 "SESSION_CLIENT_VERSION",
276 &version_num.to_string(),
277 0,
278 )?;
279 write_key_value(
280 &mut writer,
281 "AUTH_ALTER_SESSION",
282 "ALTER SESSION SET TIME_ZONE='+00:00'\0",
283 1,
284 )?;
285 if let Some(edition) = edition {
289 write_key_value(&mut writer, "AUTH_ORA_EDITION", edition, 0)?;
290 }
291 for (namespace, name, value) in app_context {
292 write_key_value(&mut writer, "AUTH_APPCTX_NSPACE\0", namespace, 0)?;
293 write_key_value(&mut writer, "AUTH_APPCTX_ATTR\0", name, 0)?;
294 write_key_value(&mut writer, "AUTH_APPCTX_VALUE\0", value, 0)?;
295 }
296 if !connect_string.is_empty() {
297 write_key_value(&mut writer, "AUTH_CONNECT_STRING", connect_string, 0)?;
298 }
299 Ok(writer.into_bytes())
300}
301
302pub fn build_change_password_payload_with_seq(
307 user: &str,
308 encoded_password: &str,
309 encoded_newpassword: &str,
310 seq_num: u8,
311 ttc_field_version: u8,
312) -> Result<Vec<u8>> {
313 let mut writer = TtcWriter::new();
314 writer.write_function_code_with_seq(TNS_FUNC_AUTH_PHASE_TWO, seq_num);
315 if version_gates::writes_pipeline_token(ttc_field_version) {
316 writer.write_ub8(0);
317 }
318 write_auth_header(
319 &mut writer,
320 user,
321 TNS_AUTH_MODE_WITH_PASSWORD | TNS_AUTH_MODE_CHANGE_PASSWORD,
322 2,
323 )?;
324 write_key_value(&mut writer, "AUTH_PASSWORD", encoded_password, 0)?;
325 write_key_value(&mut writer, "AUTH_NEWPASSWORD", encoded_newpassword, 0)?;
326 Ok(writer.into_bytes())
327}
328
329pub fn parse_auth_response(payload: &[u8]) -> Result<AuthResponse> {
330 parse_auth_response_with_limits(payload, ProtocolLimits::DEFAULT)
331}
332
333pub fn classic_connect_response_is_complete(
355 payload: &[u8],
356 limits: ProtocolLimits,
357) -> Result<bool> {
358 let Ok(mut reader) = TtcReader::with_limits(payload, limits) else {
359 return Ok(false);
360 };
361 while reader.remaining() > 0 {
362 let message_type = reader.read_u8()?;
363 let terminal = match message_type {
364 TNS_MSG_TYPE_PROTOCOL => match skip_protocol_message(&mut reader) {
365 Ok(_) => true,
366 Err(ProtocolError::TtcDecode(_)) => return Ok(false),
367 Err(err) => return Err(err),
368 },
369 TNS_MSG_TYPE_DATA_TYPES => match skip_data_types_response(&mut reader) {
370 Ok(()) => true,
371 Err(ProtocolError::TtcDecode(_)) => return Ok(false),
372 Err(err) => return Err(err),
373 },
374 TNS_MSG_TYPE_PARAMETER => match parse_return_parameters(&mut reader) {
375 Ok(_) => false,
376 Err(ProtocolError::TtcDecode(_)) => return Ok(false),
377 Err(err) => return Err(err),
378 },
379 TNS_MSG_TYPE_STATUS => {
380 let complete = reader.read_ub4().and_then(|_| reader.read_ub2());
381 match complete {
382 Ok(_) => true,
383 Err(ProtocolError::TtcDecode(_)) => return Ok(false),
384 Err(err) => return Err(err),
385 }
386 }
387 TNS_MSG_TYPE_SERVER_SIDE_PIGGYBACK => match skip_server_side_piggyback(&mut reader) {
388 Ok(_) => false,
389 Err(ProtocolError::TtcDecode(_)) => return Ok(false),
390 Err(err) => return Err(err),
391 },
392 TNS_MSG_TYPE_END_OF_RESPONSE => true,
393 TNS_MSG_TYPE_ERROR => match parse_server_error(&mut reader, 13) {
394 Ok(_) | Err(ProtocolError::ServerError(_)) => true,
395 Err(ProtocolError::TtcDecode(_)) => return Ok(false),
396 Err(err) => return Err(err),
397 },
398 _ => {
399 return Err(ProtocolError::UnknownMessageType {
400 message_type,
401 position: reader.position().saturating_sub(1),
402 })
403 }
404 };
405 if terminal {
406 return Ok(true);
407 }
408 }
409 Ok(false)
410}
411
412pub fn parse_auth_response_with_limits(
413 payload: &[u8],
414 limits: ProtocolLimits,
415) -> Result<AuthResponse> {
416 let mut reader = TtcReader::with_limits(payload, limits)?;
417 let mut response = AuthResponse::default();
418 while reader.remaining() > 0 {
419 let message_type = reader.read_u8()?;
420 match message_type {
421 TNS_MSG_TYPE_PROTOCOL => {
422 if let Some(capabilities) = skip_protocol_message(&mut reader)? {
423 response.capabilities = Some(capabilities);
424 }
425 }
426 TNS_MSG_TYPE_DATA_TYPES => skip_data_types_response(&mut reader)?,
427 TNS_MSG_TYPE_PARAMETER => {
428 let mut parsed = parse_return_parameters(&mut reader)?;
429 response.session_data.append(&mut parsed.session_data);
430 if parsed.verifier_type.is_some() {
431 response.verifier_type = parsed.verifier_type;
432 }
433 }
434 TNS_MSG_TYPE_STATUS => {
435 let _call_status = reader.read_ub4()?;
436 let _seq = reader.read_ub2()?;
437 }
438 TNS_MSG_TYPE_SERVER_SIDE_PIGGYBACK => {
439 let _ = skip_server_side_piggyback(&mut reader)?;
440 }
441 TNS_MSG_TYPE_END_OF_RESPONSE => break,
442 TNS_MSG_TYPE_ERROR => {
443 if let Some(message) = parse_server_error(&mut reader, 13)? {
444 return Err(ProtocolError::ServerError(message));
445 }
446 }
447 _ => {
448 return Err(ProtocolError::UnknownMessageType {
449 message_type,
450 position: reader.position().saturating_sub(1),
451 })
452 }
453 }
454 }
455 Ok(response)
456}
457
458pub(crate) fn write_auth_header(
459 writer: &mut TtcWriter,
460 user: &str,
461 auth_mode: u32,
462 num_pairs: u32,
463) -> Result<()> {
464 let user_bytes = user.as_bytes();
465 writer.write_u8(u8::from(!user_bytes.is_empty()));
466 writer.write_ub4(u32::try_from(user_bytes.len()).map_err(|_| {
467 ProtocolError::InvalidPacketLength {
468 length: user_bytes.len(),
469 minimum: 0,
470 }
471 })?);
472 writer.write_ub4(auth_mode);
473 writer.write_u8(1);
474 writer.write_ub4(num_pairs);
475 writer.write_u8(1);
476 writer.write_u8(1);
477 if !user_bytes.is_empty() {
478 writer.write_bytes_with_length(user_bytes)?;
479 }
480 Ok(())
481}
482
483pub(crate) fn write_key_value(
484 writer: &mut TtcWriter,
485 key: &str,
486 value: &str,
487 flags: u32,
488) -> Result<()> {
489 writer.write_str_two_lengths(key)?;
490 writer.write_str_two_lengths(value)?;
491 writer.write_ub4(flags);
492 Ok(())
493}
494
495pub(crate) fn parse_return_parameters(reader: &mut TtcReader<'_>) -> Result<AuthResponse> {
496 let num_params = reader.read_ub2()?;
497 reader
498 .limits()
499 .check_length_prefixed_elements(usize::from(num_params))?;
500 let mut response = AuthResponse::default();
501 for _ in 0..num_params {
502 let key = reader
503 .read_string_with_length()?
504 .ok_or(ProtocolError::TtcDecode("missing auth response key"))?;
505 let value = reader.read_string_with_length()?.unwrap_or_default();
506 if key == "AUTH_VFR_DATA" {
507 response.verifier_type = Some(reader.read_ub4()?);
508 } else {
509 let _flags = reader.read_ub4()?;
510 }
511 response.session_data.insert(key, value);
512 }
513 Ok(response)
514}
515
516#[cfg(test)]
517mod token_auth_tests {
518 use super::*;
519
520 fn read_ub4(bytes: &[u8], pos: &mut usize) -> u32 {
522 let len = bytes[*pos] as usize;
523 *pos += 1;
524 let mut value = 0u32;
525 for _ in 0..len {
526 value = (value << 8) | u32::from(bytes[*pos]);
527 *pos += 1;
528 }
529 value
530 }
531
532 fn contains(haystack: &[u8], needle: &[u8]) -> bool {
533 haystack.windows(needle.len()).any(|w| w == needle)
534 }
535
536 #[test]
541 fn token_message_carries_auth_token_not_password() {
542 let mut out = Vec::new();
543 append_auth_phase_two_token(
544 &mut out,
545 "scott",
546 "HEADER.PAYLOAD.SIG",
547 "drv",
548 300_000_000,
549 "cs",
550 None,
551 )
552 .unwrap();
553
554 assert_eq!(out[0], TNS_MSG_TYPE_FUNCTION);
556 assert_eq!(out[1], TNS_FUNC_AUTH_PHASE_TWO);
557 assert_eq!(out[3], 1, "user is present");
559 let mut pos = 4;
560 assert_eq!(read_ub4(&out, &mut pos), 5, "user length = len(\"scott\")");
561 assert_eq!(
562 read_ub4(&out, &mut pos),
563 TNS_AUTH_MODE_LOGON,
564 "token auth uses LOGON only — never the WITH_PASSWORD bit"
565 );
566 assert_eq!(out[pos], 1); pos += 1;
568 assert_eq!(
569 read_ub4(&out, &mut pos),
570 6,
571 "AUTH_TOKEN + 4 session pairs + AUTH_CONNECT_STRING"
572 );
573
574 assert!(contains(&out, b"AUTH_TOKEN"));
575 assert!(
576 contains(&out, b"HEADER.PAYLOAD.SIG"),
577 "the token value is sent"
578 );
579 assert!(contains(&out, b"AUTH_CONNECT_STRING"));
580 assert!(
581 !contains(&out, b"AUTH_PASSWORD") && !contains(&out, b"AUTH_SESSKEY"),
582 "token auth must not send any password material"
583 );
584 }
585
586 #[test]
589 fn token_message_pair_count_without_connect_string() {
590 let mut out = Vec::new();
591 append_auth_phase_two_token(&mut out, "u", "tok", "drv", 1, "", None).unwrap();
592 let mut pos = 4;
593 let _user_len = read_ub4(&out, &mut pos);
594 let _auth_mode = read_ub4(&out, &mut pos);
595 pos += 1; assert_eq!(read_ub4(&out, &mut pos), 5, "AUTH_TOKEN + 4 session pairs");
597 assert!(!contains(&out, b"AUTH_CONNECT_STRING"));
598 }
599
600 #[test]
605 fn token_message_carries_iam_proof_of_possession() {
606 let mut out = Vec::new();
607 append_auth_phase_two_token_with_pop(
608 &mut out,
609 "OMCP_IAM",
610 "HEADER.PAYLOAD.SIG",
611 "drv",
612 1,
613 "",
614 None,
615 Some(TokenPop {
616 header: "date: Fri, 17 Jul 2026 07:00:00 GMT\n(request-target): svc\nhost: h:1522",
617 signature: "c2lnbmF0dXJl",
618 }),
619 )
620 .unwrap();
621 let mut pos = 4;
622 let _user_len = read_ub4(&out, &mut pos);
623 assert_eq!(
624 read_ub4(&out, &mut pos),
625 TNS_AUTH_MODE_LOGON | TNS_AUTH_MODE_IAM_TOKEN,
626 "IAM proof-of-possession sets the IAM_TOKEN mode bit alongside LOGON"
627 );
628 pos += 1; assert_eq!(
630 read_ub4(&out, &mut pos),
631 7,
632 "AUTH_TOKEN + 4 session pairs + AUTH_HEADER + AUTH_SIGNATURE"
633 );
634 assert!(contains(&out, b"AUTH_TOKEN"));
635 assert!(contains(&out, b"AUTH_HEADER"));
636 assert!(contains(&out, b"AUTH_SIGNATURE"));
637 assert!(
638 contains(&out, b"(request-target): svc"),
639 "header is sent verbatim"
640 );
641 assert!(contains(&out, b"c2lnbmF0dXJl"), "base64 signature is sent");
642 }
643
644 #[test]
648 fn token_message_carries_edition() {
649 let mut out = Vec::new();
650 append_auth_phase_two_token(&mut out, "u", "tok", "drv", 1, "", Some("E_TEST")).unwrap();
651 let mut pos = 4;
652 let _user_len = read_ub4(&out, &mut pos);
653 let _auth_mode = read_ub4(&out, &mut pos);
654 pos += 1; assert_eq!(
656 read_ub4(&out, &mut pos),
657 6,
658 "AUTH_TOKEN + 4 session pairs + AUTH_ORA_EDITION"
659 );
660 assert!(contains(&out, b"AUTH_ORA_EDITION"));
661 assert!(contains(&out, b"E_TEST"), "the edition value is sent");
662
663 let mut out2 = Vec::new();
665 append_auth_phase_two_token(&mut out2, "u", "tok", "drv", 1, "cs", Some("E_TEST")).unwrap();
666 let mut p = 4;
667 let _ = read_ub4(&out2, &mut p);
668 let _ = read_ub4(&out2, &mut p);
669 p += 1;
670 assert_eq!(read_ub4(&out2, &mut p), 7, "+ AUTH_CONNECT_STRING");
671 }
672}