|
| 1 | +use picky_krb::crypto::CipherSuite; |
| 2 | +use picky_krb::messages::KrbPrivMessage; |
| 3 | +use rand::rngs::OsRng; |
| 4 | +use rand::Rng; |
| 5 | + |
| 6 | +use crate::builders::ChangePassword; |
| 7 | +use crate::generator::YieldPointLocal; |
| 8 | +use crate::kerberos::client::extractors::{ |
| 9 | + extract_encryption_params_from_as_rep, extract_session_key_from_as_rep, extract_status_code_from_krb_priv_response, |
| 10 | +}; |
| 11 | +use crate::kerberos::client::generators::{ |
| 12 | + generate_as_req_kdc_body, generate_authenticator, generate_krb_priv_request, get_client_principal_name_type, |
| 13 | + get_client_principal_realm, EncKey, GenerateAsPaDataOptions, GenerateAsReqOptions, GenerateAuthenticatorOptions, |
| 14 | +}; |
| 15 | +use crate::kerberos::pa_datas::AsReqPaDataOptions; |
| 16 | +use crate::kerberos::utils::{serialize_message, unwrap_hostname}; |
| 17 | +use crate::kerberos::{client, CHANGE_PASSWORD_SERVICE_NAME, DEFAULT_ENCRYPTION_TYPE, KADMIN}; |
| 18 | +use crate::utils::generate_random_symmetric_key; |
| 19 | +use crate::{ClientRequestFlags, Error, ErrorKind, Kerberos, Result}; |
| 20 | + |
| 21 | +/// [Kerberos Change Password and Set Password Protocols](https://datatracker.ietf.org/doc/html/rfc3244#section-2) |
| 22 | +/// "The service accepts requests on UDP port 464 and TCP port 464 as well." |
| 23 | +const KPASSWD_PORT: u16 = 464; |
| 24 | + |
| 25 | +#[instrument(level = "debug", ret, fields(state = ?client.state), skip(client, change_password))] |
| 26 | +pub async fn change_password<'a>( |
| 27 | + client: &'a mut Kerberos, |
| 28 | + yield_point: &mut YieldPointLocal, |
| 29 | + change_password: ChangePassword<'a>, |
| 30 | +) -> Result<()> { |
| 31 | + let username = &change_password.account_name; |
| 32 | + let domain = &change_password.domain_name; |
| 33 | + let password = &change_password.old_password; |
| 34 | + |
| 35 | + let salt = format!("{}{}", domain, username); |
| 36 | + |
| 37 | + let cname_type = get_client_principal_name_type(username, domain); |
| 38 | + let realm = &get_client_principal_realm(username, domain); |
| 39 | + let hostname = unwrap_hostname(client.config.client_computer_name.as_deref())?; |
| 40 | + |
| 41 | + let options = GenerateAsReqOptions { |
| 42 | + realm, |
| 43 | + username, |
| 44 | + cname_type, |
| 45 | + snames: &[KADMIN, CHANGE_PASSWORD_SERVICE_NAME], |
| 46 | + // 4 = size of u32 |
| 47 | + nonce: &OsRng.gen::<u32>().to_ne_bytes(), |
| 48 | + hostname: &hostname, |
| 49 | + context_requirements: ClientRequestFlags::empty(), |
| 50 | + }; |
| 51 | + let kdc_req_body = generate_as_req_kdc_body(&options)?; |
| 52 | + |
| 53 | + let pa_data_options = AsReqPaDataOptions::AuthIdentity(GenerateAsPaDataOptions { |
| 54 | + password: password.as_ref(), |
| 55 | + salt: salt.as_bytes().to_vec(), |
| 56 | + enc_params: client.encryption_params.clone(), |
| 57 | + with_pre_auth: false, |
| 58 | + }); |
| 59 | + |
| 60 | + let as_rep = client::as_exchange(client, yield_point, &kdc_req_body, pa_data_options).await?; |
| 61 | + |
| 62 | + debug!("AS exchange finished successfully."); |
| 63 | + |
| 64 | + client.realm = Some(as_rep.0.crealm.0.to_string()); |
| 65 | + |
| 66 | + let (encryption_type, salt) = extract_encryption_params_from_as_rep(&as_rep)?; |
| 67 | + debug!(?encryption_type, "Negotiated encryption type"); |
| 68 | + |
| 69 | + client.encryption_params.encryption_type = Some(CipherSuite::try_from(usize::from(encryption_type))?); |
| 70 | + |
| 71 | + let session_key = extract_session_key_from_as_rep(&as_rep, &salt, password.as_ref(), &client.encryption_params)?; |
| 72 | + |
| 73 | + let seq_num = client.next_seq_number(); |
| 74 | + |
| 75 | + let enc_type = client |
| 76 | + .encryption_params |
| 77 | + .encryption_type |
| 78 | + .as_ref() |
| 79 | + .unwrap_or(&DEFAULT_ENCRYPTION_TYPE); |
| 80 | + let authenticator_seb_key = generate_random_symmetric_key(enc_type, &mut OsRng); |
| 81 | + |
| 82 | + let authenticator = generate_authenticator(GenerateAuthenticatorOptions { |
| 83 | + kdc_rep: &as_rep.0, |
| 84 | + seq_num: Some(seq_num), |
| 85 | + sub_key: Some(EncKey { |
| 86 | + key_type: enc_type.clone(), |
| 87 | + key_value: authenticator_seb_key, |
| 88 | + }), |
| 89 | + checksum: None, |
| 90 | + channel_bindings: client.channel_bindings.as_ref(), |
| 91 | + extensions: Vec::new(), |
| 92 | + })?; |
| 93 | + |
| 94 | + let krb_priv = generate_krb_priv_request( |
| 95 | + as_rep.0.ticket.0, |
| 96 | + &session_key, |
| 97 | + change_password.new_password.as_ref().as_bytes(), |
| 98 | + &authenticator, |
| 99 | + &client.encryption_params, |
| 100 | + seq_num, |
| 101 | + &hostname, |
| 102 | + )?; |
| 103 | + |
| 104 | + if let Some((_realm, mut kdc_url)) = client.get_kdc() { |
| 105 | + kdc_url |
| 106 | + .set_port(Some(KPASSWD_PORT)) |
| 107 | + .map_err(|_| Error::new(ErrorKind::InvalidParameter, "Cannot set port for KDC URL"))?; |
| 108 | + |
| 109 | + let response = client.send(yield_point, &serialize_message(&krb_priv)?).await?; |
| 110 | + trace!(?response, "Change password raw response"); |
| 111 | + |
| 112 | + if response.len() < 4 { |
| 113 | + return Err(Error::new( |
| 114 | + ErrorKind::InternalError, |
| 115 | + "the KDC reply message is too small: expected at least 4 bytes", |
| 116 | + )); |
| 117 | + } |
| 118 | + |
| 119 | + let krb_priv_response = KrbPrivMessage::deserialize(&response[4..]).map_err(|err| { |
| 120 | + Error::new( |
| 121 | + ErrorKind::InvalidToken, |
| 122 | + format!("cannot deserialize krb_priv_response: {:?}", err), |
| 123 | + ) |
| 124 | + })?; |
| 125 | + |
| 126 | + let result_status = extract_status_code_from_krb_priv_response( |
| 127 | + &krb_priv_response.krb_priv, |
| 128 | + &authenticator.0.subkey.0.as_ref().unwrap().0.key_value.0 .0, |
| 129 | + &client.encryption_params, |
| 130 | + )?; |
| 131 | + |
| 132 | + if result_status != 0 { |
| 133 | + return Err(Error::new( |
| 134 | + ErrorKind::WrongCredentialHandle, |
| 135 | + format!("unsuccessful krb result code: {}. expected 0", result_status), |
| 136 | + )); |
| 137 | + } |
| 138 | + } else { |
| 139 | + return Err(Error::new( |
| 140 | + ErrorKind::NoAuthenticatingAuthority, |
| 141 | + "no KDC server found".to_owned(), |
| 142 | + )); |
| 143 | + } |
| 144 | + |
| 145 | + Ok(()) |
| 146 | +} |
0 commit comments