#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::{boxed::Box, vec::Vec};
use crate::asn1::OctetString;
use crate::constants::TIGHTBEAM_AAD_DOMAIN_TAG;
use crate::crypto::aead::{KeyInit, RuntimeAead};
use crate::crypto::ecies::EciesEphemeral;
use crate::crypto::ecies::{encrypt, EciesMessageOps, EciesPublicKeyOps};
use crate::crypto::key::SigningKeyProvider;
use crate::crypto::profiles::{CryptoProvider, SecurityProfileDesc};
use crate::crypto::sign::elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint};
use crate::crypto::sign::elliptic_curve::{AffinePoint, Curve, CurveArithmetic, PublicKey};
use crate::crypto::sign::PrehashVerifier;
use crate::crypto::sign::SignatureEncoding;
use crate::crypto::x509::policy::CertificateValidation;
use crate::crypto::x509::utils::validate_certificate_expiry;
use crate::der::{Decode, Encode};
use crate::random::generate_nonce;
use crate::zeroize::{Zeroize, Zeroizing};
use crate::transport::handshake::error::HandshakeError;
use crate::transport::handshake::negotiation::SecurityOffer;
use crate::transport::handshake::state::HandshakeInvariant;
use crate::transport::handshake::state::{ClientHandshakeState, ClientStateMachine};
use crate::transport::handshake::utils::{
compute_client_auth_digest, compute_transcript_digest, octet_string_to_32_byte_array, validate_state,
};
use crate::transport::handshake::{Arc, ClientHandshakeProtocol, ClientHello, ClientKeyExchange, ServerHandshake};
use crate::transport::handshake::{HandshakeAlertHandler, HandshakeFinalization}; use crate::x509::Certificate;
pub struct EciesHandshakeClient<P, M>
where
P: CryptoProvider,
{
state: ClientStateMachine,
client_random: Option<[u8; 32]>,
client_hello: Option<Vec<u8>>,
base_session_key: Option<[u8; 32]>,
server_random: Option<[u8; 32]>,
transcript_hash: Option<[u8; 32]>,
aad_domain_tag: Option<&'static [u8]>,
security_offer: Option<SecurityOffer>,
selected_profile: Option<SecurityProfileDesc>,
certificate_validator: Option<Arc<dyn CertificateValidation>>,
client_certificate: Option<Arc<Certificate>>,
client_key_provider: Option<Arc<dyn crate::crypto::key::SigningKeyProvider>>,
_phantom_provider: ::core::marker::PhantomData<P>,
_phantom_message: ::core::marker::PhantomData<M>,
invariants: HandshakeInvariant,
}
pub trait ExtractVerifyingKey: Sized {
fn extract_from_certificate(cert: &Certificate) -> Result<Self, HandshakeError>;
}
impl<P, M> EciesHandshakeClient<P, M>
where
P: CryptoProvider,
P::Curve: Curve + CurveArithmetic,
<P::Curve as Curve>::FieldBytesSize: ModulusSize,
AffinePoint<P::Curve>: FromEncodedPoint<P::Curve> + ToEncodedPoint<P::Curve>,
PublicKey<P::Curve>: EciesPublicKeyOps,
<PublicKey<P::Curve> as EciesPublicKeyOps>::SecretKey: EciesEphemeral<PublicKey = PublicKey<P::Curve>>,
P::Signature: SignatureEncoding,
for<'a> P::Signature: TryFrom<&'a [u8]>,
for<'a> <P::Signature as TryFrom<&'a [u8]>>::Error: Into<HandshakeError>,
P::VerifyingKey: PrehashVerifier<P::Signature> + ExtractVerifyingKey,
P::AeadCipher: KeyInit,
M: EciesMessageOps,
{
pub fn new(aad_domain_tag: Option<&'static [u8]>) -> Self {
Self {
state: ClientStateMachine::default(),
client_random: None,
client_hello: None,
base_session_key: None,
server_random: None,
transcript_hash: None,
aad_domain_tag: aad_domain_tag.or(Some(TIGHTBEAM_AAD_DOMAIN_TAG)),
security_offer: None, selected_profile: None,
certificate_validator: None,
client_certificate: None,
client_key_provider: None,
invariants: HandshakeInvariant::default(),
_phantom_provider: ::core::marker::PhantomData,
_phantom_message: ::core::marker::PhantomData,
}
}
pub fn new_with_identity(
aad_domain_tag: Option<&'static [u8]>,
client_certificate: Option<Arc<Certificate>>,
client_key_provider: Option<Arc<dyn crate::crypto::key::SigningKeyProvider>>,
) -> Self {
Self {
state: ClientStateMachine::default(),
client_random: None,
client_hello: None,
base_session_key: None,
server_random: None,
transcript_hash: None,
aad_domain_tag: aad_domain_tag.or(Some(TIGHTBEAM_AAD_DOMAIN_TAG)),
security_offer: None, selected_profile: None,
certificate_validator: None,
client_certificate,
client_key_provider,
invariants: HandshakeInvariant::default(),
_phantom_provider: ::core::marker::PhantomData,
_phantom_message: ::core::marker::PhantomData,
}
}
pub fn with_certificate_validator(mut self, validator: Arc<dyn CertificateValidation>) -> Self {
self.certificate_validator = Some(validator);
self
}
pub fn with_client_identity(
mut self,
certificate: Arc<Certificate>,
key_provider: Arc<dyn SigningKeyProvider>,
) -> Self {
self.client_certificate = Some(certificate);
self.client_key_provider = Some(key_provider);
self
}
pub fn with_security_offer(mut self, offer: SecurityOffer) -> Self {
self.security_offer = Some(offer);
self
}
fn validate_expected_state(&self, expected: ClientHandshakeState) -> Result<(), HandshakeError> {
validate_state(self.state.state(), expected)
}
fn validate_and_extract_server_handshake(
&self,
server_handshake_der: &[u8],
) -> Result<ServerHandshake, HandshakeError> {
let server_handshake = ServerHandshake::from_der(server_handshake_der)?;
let validator = self.certificate_validator.as_ref().ok_or(HandshakeError::MissingTrustStore)?;
validate_certificate_expiry(&server_handshake.certificate)?;
validator.evaluate(&server_handshake.certificate)?;
Ok(server_handshake)
}
fn extract_server_random(&mut self, server_handshake: &ServerHandshake) -> Result<(), HandshakeError> {
let server_random = octet_string_to_32_byte_array(&server_handshake.server_random)?;
self.server_random = Some(server_random);
Ok(())
}
fn compute_and_store_transcript_hash(&mut self, server_handshake: &ServerHandshake) -> Result<(), HandshakeError> {
let client_hello = self.client_hello.as_deref().ok_or(HandshakeError::InvalidState)?;
let server_random = self.server_random.ok_or(HandshakeError::InvalidState)?;
let spki_bytes = server_handshake
.certificate
.tbs_certificate
.subject_public_key_info
.subject_public_key
.raw_bytes();
let accept_der = match &server_handshake.security_accept {
Some(accept) => accept.to_der()?,
None => Vec::new(),
};
let transcript_digest = self.compute_transcript_hash(client_hello, &server_random, spki_bytes, &accept_der)?;
self.transcript_hash = Some(transcript_digest);
self.invariants.lock_transcript()?;
Ok(())
}
fn generate_base_session_key(&mut self) -> Result<(), HandshakeError> {
let base_key = generate_nonce::<32>(None)?;
self.base_session_key = Some(base_key);
Ok(())
}
pub fn build_client_hello(&mut self) -> Result<Vec<u8>, HandshakeError> {
self.validate_expected_state(ClientHandshakeState::Init)?;
let client_random = generate_nonce::<32>(None)?;
self.client_random = Some(client_random);
let client_hello = ClientHello {
client_random: OctetString::new(client_random)?,
security_offer: self.security_offer.clone(),
};
let client_hello_der = client_hello.to_der()?;
self.client_hello = Some(client_hello_der.clone());
self.state.transition(ClientHandshakeState::HelloSent)?;
Ok(client_hello_der)
}
pub async fn process_server_handshake(&mut self, server_handshake_der: &[u8]) -> Result<Vec<u8>, HandshakeError> {
self.validate_expected_state(ClientHandshakeState::HelloSent)?;
let _client_random_check = self.client_random.ok_or(HandshakeError::InvalidState)?;
self.state.transition(ClientHandshakeState::ServerHelloReceived)?;
let server_handshake = self.validate_and_extract_server_handshake(server_handshake_der)?;
self.validate_profile_selection(&server_handshake)?;
self.extract_server_random(&server_handshake)?;
self.verify_server_handshake_signature(&server_handshake)?;
let encrypted_bytes = self.generate_and_encrypt_session_key(&server_handshake)?;
let (client_certificate, client_signature) =
self.prepare_client_auth(&server_handshake, &encrypted_bytes).await?;
let client_kex = ClientKeyExchange {
encrypted_data: OctetString::new(encrypted_bytes)?,
#[cfg(feature = "x509")]
client_certificate,
#[cfg(feature = "x509")]
client_signature,
};
self.state.transition(ClientHandshakeState::KeyExchangeSent)?;
Ok(client_kex.to_der()?)
}
fn validate_profile_selection(&mut self, server_handshake: &ServerHandshake) -> Result<(), HandshakeError> {
let accept = server_handshake.security_accept.as_ref().ok_or(HandshakeError::InvalidState)?;
match &self.security_offer {
Some(offer) => {
if !offer.profiles.contains(&accept.profile) {
return Err(HandshakeError::InvalidProfileSelection);
}
self.selected_profile = Some(accept.profile);
}
None => {
self.selected_profile = Some(accept.profile);
}
}
Ok(())
}
fn verify_server_handshake_signature(&mut self, server_handshake: &ServerHandshake) -> Result<(), HandshakeError> {
let verifying_key = self.extract_verifying_key(&server_handshake.certificate)?;
self.compute_and_store_transcript_hash(server_handshake)?;
let transcript_digest = self.transcript_hash.ok_or(HandshakeError::InvalidState)?;
self.verify_server_signature(&verifying_key, &transcript_digest, server_handshake.signature.as_bytes())
}
fn generate_and_encrypt_session_key(
&mut self,
server_handshake: &ServerHandshake,
) -> Result<Vec<u8>, HandshakeError> {
self.generate_base_session_key()?;
let base_key = self.base_session_key.ok_or(HandshakeError::InvalidState)?;
let client_random = self.client_random.ok_or(HandshakeError::InvalidState)?;
self.perform_ecies_encryption(&base_key, &client_random, &server_handshake.certificate, self.aad_domain_tag)
}
async fn prepare_client_auth(
&self,
server_handshake: &ServerHandshake,
encrypted_data: &[u8],
) -> Result<(Option<Certificate>, Option<OctetString>), HandshakeError> {
let transcript_digest = self.transcript_hash.ok_or(HandshakeError::InvalidState)?;
let cert = match (&self.client_certificate, server_handshake.client_cert_required) {
(Some(cert), _) => cert,
(None, true) => return Err(HandshakeError::MutualAuthRequired),
(None, false) => return Ok((None, None)),
};
let key_provider = match (&self.client_key_provider, server_handshake.client_cert_required) {
(Some(provider), _) => provider,
(None, true) => return Err(HandshakeError::MutualAuthRequired),
(None, false) => return Err(HandshakeError::InvalidState),
};
let cert_der = cert.to_der()?;
let auth_digest = compute_client_auth_digest::<P::Digest>(&transcript_digest, encrypted_data, &cert_der)?;
let signature_bytes = key_provider.sign_prehash(&auth_digest).await?;
let cert = Certificate::clone(cert);
let signature = OctetString::new(signature_bytes)?;
Ok((Some(cert), Some(signature)))
}
pub fn complete(&mut self) -> Result<P::AeadCipher, HandshakeError> {
self.validate_expected_state(ClientHandshakeState::KeyExchangeSent)?;
let base_key = self.base_session_key.as_ref().ok_or(HandshakeError::InvalidState)?;
let client_random = self.client_random.as_ref().ok_or(HandshakeError::InvalidState)?;
let server_random = self.server_random.as_ref().ok_or(HandshakeError::InvalidState)?;
let mut salt = Zeroizing::new([0u8; 64]);
salt[..32].copy_from_slice(client_random);
salt[32..].copy_from_slice(server_random);
let session_key = self.derive_session_aead(base_key, salt.as_slice())?;
self.invariants.derive_aead_once()?;
self.state.transition(ClientHandshakeState::Completed)?;
self.base_session_key.zeroize();
self.client_random.zeroize();
self.server_random.zeroize();
Ok(session_key)
}
pub fn state(&self) -> ClientHandshakeState {
self.state.state()
}
pub fn is_complete(&self) -> bool {
self.state.state().is_completed()
}
pub fn transcript_hash(&self) -> Option<[u8; 32]> {
self.transcript_hash
}
fn extract_verifying_key(&self, cert: &Certificate) -> Result<P::VerifyingKey, HandshakeError> {
P::VerifyingKey::extract_from_certificate(cert)
}
fn compute_transcript_hash(
&self,
client_hello: &[u8],
server_random: &[u8; 32],
spki_bytes: &[u8],
accept_der: &[u8],
) -> Result<[u8; 32], HandshakeError> {
let mut data = Vec::with_capacity(client_hello.len() + 32 + spki_bytes.len() + accept_der.len());
data.extend_from_slice(client_hello);
data.extend_from_slice(server_random);
data.extend_from_slice(spki_bytes);
data.extend_from_slice(accept_der);
compute_transcript_digest::<P::Digest>(&data)
}
fn verify_server_signature(
&self,
verifying_key: &P::VerifyingKey,
digest: &[u8; 32],
signature_bytes: &[u8],
) -> Result<(), HandshakeError> {
let signature = P::Signature::try_from(signature_bytes).map_err(|e| e.into())?;
verifying_key.verify_prehash(digest, &signature)?;
Ok(())
}
fn perform_ecies_encryption(
&self,
base_key: &[u8; 32],
client_random: &[u8; 32],
server_certificate: &Certificate,
associated_data: Option<&[u8]>,
) -> Result<Vec<u8>, HandshakeError> {
let mut plaintext = Zeroizing::new([0u8; 64]);
plaintext[..32].copy_from_slice(base_key);
plaintext[32..].copy_from_slice(client_random);
let recipient_pubkey = PublicKey::<P::Curve>::from_sec1_bytes(
server_certificate
.tbs_certificate
.subject_public_key_info
.subject_public_key
.raw_bytes(),
)?;
let encrypted_message = encrypt::<_, _, _, M, P::Kdf, P::AeadCipher>(
&recipient_pubkey,
plaintext.as_slice(),
associated_data,
Some(&mut rand_core::OsRng),
)?;
Ok(encrypted_message.to_bytes())
}
}
impl<P, M> HandshakeFinalization<P> for EciesHandshakeClient<P, M>
where
P: CryptoProvider,
{
fn selected_profile(&self) -> Option<SecurityProfileDesc> {
self.selected_profile
}
}
impl<P, M> HandshakeAlertHandler for EciesHandshakeClient<P, M> where P: CryptoProvider {}
impl<P, M> ClientHandshakeProtocol for EciesHandshakeClient<P, M>
where
P: CryptoProvider + Send + Sync,
P::Curve: Curve + CurveArithmetic,
<P::Curve as Curve>::FieldBytesSize: ModulusSize,
AffinePoint<P::Curve>: FromEncodedPoint<P::Curve> + ToEncodedPoint<P::Curve>,
PublicKey<P::Curve>: EciesPublicKeyOps,
<PublicKey<P::Curve> as EciesPublicKeyOps>::SecretKey: EciesEphemeral<PublicKey = PublicKey<P::Curve>>,
P::Signature: SignatureEncoding + Send + Sync,
for<'a> P::Signature: TryFrom<&'a [u8]>,
for<'a> <P::Signature as TryFrom<&'a [u8]>>::Error: Into<HandshakeError>,
P::VerifyingKey: PrehashVerifier<P::Signature> + ExtractVerifyingKey + Send + Sync,
P::AeadCipher: KeyInit + Send + Sync + 'static,
M: EciesMessageOps + Send + Sync,
{
type Error = HandshakeError;
fn start<'a>(
&'a mut self,
) -> core::pin::Pin<Box<dyn core::future::Future<Output = Result<Vec<u8>, Self::Error>> + Send + 'a>> {
Box::pin(async move { self.build_client_hello() })
}
fn handle_response<'a, 'b>(
&'a mut self,
msg: &'b [u8],
) -> core::pin::Pin<Box<dyn core::future::Future<Output = Result<Option<Vec<u8>>, Self::Error>> + Send + 'a>>
where
'b: 'a,
{
Box::pin(async move {
let client_kex = self.process_server_handshake(msg).await?;
Ok(Some(client_kex))
})
}
#[cfg(feature = "aead")]
fn complete<'a>(
&'a mut self,
) -> core::pin::Pin<Box<dyn core::future::Future<Output = Result<RuntimeAead, Self::Error>> + Send + 'a>> {
Box::pin(async move {
let profile = self.selected_profile.ok_or(HandshakeError::InvalidState)?;
let aead_oid = profile.aead.ok_or(HandshakeError::InvalidState)?;
let cipher = EciesHandshakeClient::complete(self)?;
Ok(RuntimeAead::new(cipher, aead_oid))
})
}
fn is_complete(&self) -> bool {
self.state.state().is_completed()
}
fn selected_profile(&self) -> Option<SecurityProfileDesc> {
self.selected_profile
}
}
#[cfg(feature = "secp256k1")]
impl ExtractVerifyingKey for crate::crypto::sign::ecdsa::Secp256k1VerifyingKey {
fn extract_from_certificate(cert: &Certificate) -> Result<Self, HandshakeError> {
let public_key_bytes = crate::crypto::x509::utils::extract_verifying_key_bytes(cert);
let public_key = k256::PublicKey::from_sec1_bytes(public_key_bytes)?;
Ok(Self::from(public_key))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::ecies::Secp256k1EciesMessage;
use crate::crypto::profiles::{DefaultCryptoProvider, SecurityProfileDesc};
use crate::crypto::sign::ecdsa::Secp256k1Signature;
use crate::crypto::sign::PrehashSigner;
use crate::der::Encode;
use crate::transport::handshake::negotiation::{SecurityAccept, SecurityOffer};
use crate::transport::handshake::tests::*;
use crate::transport::handshake::ServerHandshake;
use crate::oids::{
AES_256_GCM, AES_256_WRAP, CURVE_SECP256K1, HASH_SHA3_256, HASH_SHA3_384, HASH_SHA3_512,
SIGNER_ECDSA_WITH_SHA3_512,
};
#[tokio::test]
async fn test_client_state_flow() -> Result<(), Box<dyn core::error::Error>> {
let test_cert = create_test_certificate();
let mut client = TestEciesClientBuilder::new()
.with_trusted_certificate(test_cert.certificate.clone())
.build();
assert_eq!(client.state(), ClientHandshakeState::Init);
let client_hello_der = client.build_client_hello()?;
assert_eq!(client.state(), ClientHandshakeState::HelloSent); assert!(client.client_random.is_some());
let server_random = crate::random::generate_nonce::<32>(None)?;
let accept_der = SecurityAccept::new(create_default_test_profile()).to_der()?;
let transcript_hash = compute_test_transcript_hash(
&client_hello_der,
&server_random,
test_cert
.certificate
.tbs_certificate
.subject_public_key_info
.subject_public_key
.raw_bytes(),
&accept_der,
);
let signature_bytes: Secp256k1Signature = test_cert.signing_key.sign_prehash(&transcript_hash)?;
let server_handshake_der =
create_test_server_handshake(&test_cert.certificate, &server_random, &signature_bytes.to_bytes())?;
let client_kex_der = client.process_server_handshake(&server_handshake_der).await?;
assert_eq!(client.state(), ClientHandshakeState::KeyExchangeSent);
assert!(client.base_session_key.is_some());
assert!(client.transcript_hash.is_some());
let _client_kex = ClientKeyExchange::from_der(&client_kex_der)?;
let _session_key = client.complete()?;
assert!(client.is_complete());
assert_eq!(client.state(), ClientHandshakeState::Completed);
Ok(())
}
#[tokio::test]
async fn test_missing_validator_fails_closed() -> Result<(), Box<dyn core::error::Error>> {
let mut client = TestEciesClientBuilder::new().build();
let client_hello_der = client.build_client_hello()?;
let test_cert = create_test_certificate();
let server_random = crate::random::generate_nonce::<32>(None)?;
let accept_der = SecurityAccept::new(create_default_test_profile()).to_der()?;
let transcript_hash = compute_test_transcript_hash(
&client_hello_der,
&server_random,
test_cert
.certificate
.tbs_certificate
.subject_public_key_info
.subject_public_key
.raw_bytes(),
&accept_der,
);
let signature_bytes: Secp256k1Signature = test_cert.signing_key.sign_prehash(&transcript_hash)?;
let server_handshake_der =
create_test_server_handshake(&test_cert.certificate, &server_random, &signature_bytes.to_bytes())?;
let result = client.process_server_handshake(&server_handshake_der).await;
assert!(matches!(result, Err(HandshakeError::MissingTrustStore)));
Ok(())
}
#[tokio::test]
async fn test_invalid_state_transitions() -> Result<(), Box<dyn core::error::Error>> {
let mut client = TestEciesClientBuilder::new().build();
let result = client.process_server_handshake(&[]).await;
assert!(result.is_err());
let _client_hello = client.build_client_hello()?;
assert_eq!(client.state(), ClientHandshakeState::HelloSent);
let result = client.complete();
assert!(result.is_err());
Ok(())
}
#[tokio::test]
async fn test_client_profile_validation() -> Result<(), Box<dyn core::error::Error>> {
let mk_profile = |id: u8| SecurityProfileDesc {
digest: Some(match id {
1 => HASH_SHA3_256,
2 => HASH_SHA3_384,
_ => HASH_SHA3_512,
}),
aead: Some(AES_256_GCM),
aead_key_size: Some(32),
signature: Some(SIGNER_ECDSA_WITH_SHA3_512),
kdf: Some(HASH_SHA3_256), curve: Some(CURVE_SECP256K1),
key_wrap: Some(AES_256_WRAP),
kem: None,
};
let (p_a, p_b, p_c) = (mk_profile(1), mk_profile(2), mk_profile(3));
let test_cert = create_test_certificate();
#[allow(clippy::type_complexity)]
let setup_client = |offer: Option<SecurityOffer>| -> Result<
(EciesHandshakeClient<DefaultCryptoProvider, Secp256k1EciesMessage>, Vec<u8>),
Box<dyn std::error::Error>,
> {
let mut client = TestEciesClientBuilder::new()
.with_trusted_certificate(test_cert.certificate.clone())
.build();
if let Some(offer) = offer {
client = client.with_security_offer(offer);
}
let hello = client.build_client_hello()?;
Ok((client, hello))
};
let create_server_response = |client_hello_der: &[u8],
server_random: [u8; 32],
accepted_profile: &SecurityProfileDesc|
-> Result<Vec<u8>, Box<dyn core::error::Error>> {
let accept_der = SecurityAccept::new(*accepted_profile).to_der()?;
let transcript_hash = compute_test_transcript_hash(
client_hello_der,
&server_random,
test_cert
.certificate
.tbs_certificate
.subject_public_key_info
.subject_public_key
.raw_bytes(),
&accept_der,
);
let signature: Secp256k1Signature = test_cert.signing_key.sign_prehash(&transcript_hash)?;
let signature_bytes = signature.to_bytes().to_vec();
let response = ServerHandshake {
certificate: test_cert.certificate.clone(),
server_random: OctetString::new(server_random)?,
signature: OctetString::new(signature_bytes)?,
security_accept: Some(SecurityAccept::new(*accepted_profile)),
client_cert_required: false,
};
Ok(response.to_der()?)
};
{
let (mut client, client_hello_der) = setup_client(Some(SecurityOffer::new(vec![p_a, p_b])))?;
let server_response = create_server_response(&client_hello_der, [2u8; 32], &p_b)?;
let _kex = client.process_server_handshake(&server_response).await?;
assert_eq!(client.selected_profile, Some(p_b));
}
{
let (mut client, client_hello_der) = setup_client(Some(SecurityOffer::new(vec![p_a, p_b])))?;
let server_response = create_server_response(&client_hello_der, [3u8; 32], &p_c)?;
let result = client.process_server_handshake(&server_response).await;
assert!(matches!(result, Err(HandshakeError::InvalidProfileSelection)));
}
{
let (mut client, client_hello_der) = setup_client(None)?;
let server_response = create_server_response(&client_hello_der, [4u8; 32], &p_a)?;
let _kex = client.process_server_handshake(&server_response).await?;
assert_eq!(client.selected_profile, Some(p_a));
}
Ok(())
}
}