kmip_protocol/client/
client.rs

1//! A high level KMIP "operation" oriented client interface for request/response construction & (de)serialization.
2use std::{
3    cell::RefCell,
4    ops::{Deref, DerefMut},
5    sync::{
6        atomic::{AtomicU8, Ordering},
7        Arc, Mutex, PoisonError,
8    },
9};
10
11use kmip_ttlv::{error::ErrorKind, Config, PrettyPrinter};
12use log::trace;
13
14use crate::{
15    auth::{self, CredentialType},
16    request::to_vec,
17    tag_map,
18    types::{common::*, request, request::*, response::*, traits::*},
19};
20
21/// There was a problem sending/receiving a KMIP request/response.
22#[non_exhaustive]
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub enum Error {
25    ConfigurationError(String),
26    SerializeError(String),
27    RequestWriteError(String),
28    ResponseReadError(String),
29    DeserializeError(String),
30    ServerError(String),
31    InternalError(String),
32    ItemNotFound(String),
33    Unknown(String),
34}
35
36impl Error {
37    /// Is this a possibly transient problem with the connection to the server?
38    pub fn is_connection_error(&self) -> bool {
39        use Error::*;
40        matches!(self, RequestWriteError(_) | ResponseReadError(_))
41    }
42}
43
44impl std::error::Error for Error {}
45
46impl From<std::io::Error> for Error {
47    fn from(err: std::io::Error) -> Self {
48        Error::ServerError(format!("I/O error: {}", err))
49    }
50}
51
52impl std::fmt::Display for Error {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        match self {
55            Error::ConfigurationError(e) => f.write_fmt(format_args!("Configuration error: {}", e)),
56            Error::SerializeError(e) => f.write_fmt(format_args!("Serialize error: {}", e)),
57            Error::RequestWriteError(e) => f.write_fmt(format_args!("Request send error: {}", e)),
58            Error::ResponseReadError(e) => f.write_fmt(format_args!("Response read error: {}", e)),
59            Error::DeserializeError(e) => f.write_fmt(format_args!("Deserialize error: {}", e)),
60            Error::ServerError(e) => f.write_fmt(format_args!("Server error: {}", e)),
61            Error::InternalError(e) => f.write_fmt(format_args!("Internal error: {}", e)),
62            Error::ItemNotFound(e) => f.write_fmt(format_args!("Item not found: {}", e)),
63            Error::Unknown(e) => f.write_fmt(format_args!("Unknown error: {}", e)),
64        }
65    }
66}
67
68/// The successful or failed outcome resulting from sending a request to a KMIP server.
69pub type Result<T> = std::result::Result<T, Error>;
70
71impl<T> From<PoisonError<T>> for Error {
72    fn from(err: PoisonError<T>) -> Self {
73        Error::InternalError(err.to_string())
74    }
75}
76
77/// Use this builder to construct a [Client] struct.
78#[derive(Debug)]
79pub struct ClientBuilder<T> {
80    username: Option<String>,
81    password: Option<String>,
82    stream: T,
83    reader_config: Config,
84}
85
86impl<T> ClientBuilder<T> {
87    /// Build a [Client] struct that will read/write from/to the given stream.
88    ///
89    /// Creates a [ClientBuilder] which can be used to create a [Client] which will read/write from/to the given
90    /// stream. The stream is expected to be a type which can read from and write to an established TCP connection to
91    /// the KMIP server. In production the stream should also perform TLS de/encryption on the data read from/written
92    /// to the stream.
93    ///
94    /// The `stream` argument must implement the read and write traits which the [Client] will use to read/write
95    /// from/to the stream.
96    pub fn new(stream: T) -> Self {
97        Self {
98            username: None,
99            password: None,
100            stream,
101            reader_config: Config::default(),
102        }
103    }
104
105    /// Configure the [Client] to do include username/password authentication credentials in KMIP requests.
106    pub fn with_credentials(mut self, username: String, password: Option<String>) -> Self {
107        self.username = Some(username);
108        self.password = password;
109        self
110    }
111
112    /// Configure the [Client] to use the given reader [Config].
113    pub fn with_reader_config(mut self, reader_config: Config) -> Self {
114        self.reader_config = reader_config;
115        self
116    }
117
118    /// Build the configured [Client] struct instance.
119    pub fn build(self) -> Client<T> {
120        let mut pretty_printer = PrettyPrinter::new();
121        pretty_printer.with_tag_prefix("4200".into());
122        pretty_printer.with_tag_map(tag_map::make_kmip_tag_map());
123
124        Client {
125            username: self.username,
126            password: self.password,
127            stream: Arc::new(Mutex::new(self.stream)),
128            reader_config: self.reader_config,
129            connection_error_count: AtomicU8::new(0),
130            last_req_diag_str: RefCell::new(None),
131            last_res_diag_str: RefCell::new(None),
132            pretty_printer,
133        }
134    }
135}
136
137/// Helper macro to avoid repetetive blocks of almost identical code
138macro_rules! get_response_payload_for_type {
139    ($response:expr, $response_type:path) => {{
140        // Process the successful response
141        if let $response_type(payload) = $response {
142            Ok(payload)
143        } else {
144            Err(Error::InternalError(format!(
145                "Expected {} response payload but got: {:?}",
146                stringify!($response_type),
147                $response
148            )))
149        }
150    }};
151}
152
153/// A client for serializing KMIP and deserializing KMIP responses to/from an established read/write stream.
154///
155/// Use the [ClientBuilder] to build a [Client] instance to work with.
156#[derive(Debug)]
157pub struct Client<T> {
158    username: Option<String>,
159    password: Option<String>,
160    stream: Arc<Mutex<T>>,
161    reader_config: Config,
162    connection_error_count: AtomicU8,
163    last_req_diag_str: RefCell<Option<String>>,
164    last_res_diag_str: RefCell<Option<String>>,
165    pretty_printer: PrettyPrinter,
166}
167
168impl<T: ReadWrite> Client<T> {
169    pub fn inner(&self) -> Arc<Mutex<T>> {
170        self.stream.clone()
171    }
172
173    /// Write request bytes to the given stream and read, deserialize and sanity check the response.
174    #[maybe_async::maybe_async]
175    async fn send_and_receive(
176        &self,
177        operation: Operation,
178        reader_config: &Config,
179        req_bytes: &[u8],
180        stream: Arc<Mutex<T>>,
181    ) -> Result<ResponsePayload> {
182        let mut lock = stream.lock()?;
183        let stream = lock.deref_mut();
184
185        stream
186            .write_all(req_bytes)
187            .await
188            .map_err(|e| Error::RequestWriteError(e.to_string()))?;
189
190        // Read and deserialize the response
191        let mut res: ResponseMessage = kmip_ttlv::from_reader(stream, reader_config)
192            .await
193            .map_err(|err| match err.kind() {
194                ErrorKind::IoError(e) => Error::ResponseReadError(e.to_string()),
195                ErrorKind::ResponseSizeExceedsLimit(_) | ErrorKind::MalformedTtlv(_) => {
196                    Error::DeserializeError(err.to_string())
197                }
198                _ => Error::InternalError(err.to_string()),
199            })?;
200
201        if res.header.batch_count == 1 && res.batch_items.len() == 1 {
202            let item = &mut res.batch_items[0];
203
204            match item.result_status {
205                ResultStatus::OperationFailed => {
206                    if matches!(item.result_reason, Some(ResultReason::ItemNotFound)) {
207                        Err(Error::ItemNotFound(format!(
208                            "Operation {:?} failed: {}",
209                            operation,
210                            item.result_message.as_ref().unwrap_or(&String::new()).clone()
211                        )))
212                    } else {
213                        Err(Error::ServerError(format!(
214                            "Operation {:?} failed: {}",
215                            operation,
216                            item.result_message.as_ref().unwrap_or(&String::new()).clone()
217                        )))
218                    }
219                }
220                ResultStatus::OperationPending => Err(Error::InternalError(
221                    "Result status 'operation pending' is not supported".into(),
222                )),
223                ResultStatus::OperationUndone => Err(Error::InternalError(
224                    "Result status 'operation undone' is not supported".into(),
225                )),
226                ResultStatus::Success => {
227                    if item.operation == Some(operation) {
228                        if let Some(payload) = item.payload.take() {
229                            Ok(payload)
230                        } else {
231                            Err(Error::InternalError(
232                                "Unable to process response payload due to wrong deserialized type!".into(),
233                            ))
234                        }
235                    } else {
236                        Err(Error::InternalError(format!(
237                            "Response operation {:?} does not match request operation {}",
238                            item.operation, operation
239                        )))
240                    }
241                }
242            }
243        } else {
244            Err(Error::ServerError(format!(
245                "Expected one batch item in response but received {}",
246                res.batch_items.len()
247            )))
248        }
249    }
250
251    /// Serialize the given request to the stream and deserialize the response.
252    ///
253    /// Automatically constructs the request message wrapper around the payload including the [RequestHeader] and
254    /// [BatchItem].
255    ///
256    /// Only supports a single batch item.
257    ///
258    /// Sets the request operation to [RequestPayload::operation()].
259    ///
260    /// # Errors
261    ///
262    /// Will fail if there is a problem serializing the request, writing to or reading from the stream, deserializing
263    /// the response or if the response does not indicate operation success or contains more than one batch item.
264    ///
265    /// Currently always returns [Error::Unknown] even though richer cause information is available.
266    #[maybe_async::maybe_async]
267    pub async fn do_request(&self, payload: RequestPayload) -> Result<ResponsePayload> {
268        // Clear the diagnostic string representations of the request and response.
269        *self.last_req_diag_str.borrow_mut() = None;
270        *self.last_res_diag_str.borrow_mut() = None;
271
272        // Save a copy of the KMIP operation identifier before the request payload object is consumed by the
273        // TTLV serializer.
274        let operation = payload.operation();
275
276        // Serialize the request payload to TTLV byte form.
277        let req_bytes = to_vec(payload, self.auth()).map_err(|err| match err.kind() {
278            ErrorKind::IoError(e) => Error::SerializeError(e.to_string()),
279            _ => Error::InternalError(err.to_string()),
280        })?;
281
282        // If the caller requested that diagnostic string representations of the TTLV request and response bytes be
283        // captured then generate, record and log the diagnostic representation of the request.
284        if self.reader_config.has_buf() {
285            let diag_str = self.pretty_printer.to_diag_string(&req_bytes);
286            trace!("KMIP TTLV request: {}", &diag_str);
287            self.last_req_diag_str.borrow_mut().replace(diag_str);
288        }
289
290        // Prepare a helper closure for incrementing the number of connection errors encountered by this client.
291        let incr_err_count = |err: Error| {
292            if err.is_connection_error() {
293                let _ = self.connection_error_count.fetch_add(1, Ordering::SeqCst);
294            }
295            Err(err)
296        };
297
298        // Send the serialized request and receive (and deserialize) the response.
299        let res = self
300            .send_and_receive(operation, &self.reader_config, &req_bytes, self.stream.clone())
301            .await
302            .or_else(incr_err_count);
303
304        // If the caller requested that diagnostic string representations of the TTLV request and response bytes be
305        // captured, then generate, record and log the diagnostic representation of the response.
306        if let Some(buf) = self.reader_config.read_buf() {
307            let diag_str = self.pretty_printer.to_diag_string(&buf);
308            trace!("KMIP TTLV response: {}", &diag_str);
309            self.last_res_diag_str.borrow_mut().replace(diag_str);
310        }
311
312        res
313    }
314
315    /// Serialize a KMIP 1.0 [Query](https://docs.oasis-open.org/kmip/spec/v1.0/os/kmip-spec-1.0-os.html#_Toc262581232) request.
316    ///
317    /// See also: [do_request()](Self::do_request())
318    #[maybe_async::maybe_async]
319    pub async fn query(&self) -> Result<QueryResponsePayload> {
320        // Setup the request
321        let wanted_info = vec![
322            QueryFunction::QueryOperations,
323            QueryFunction::QueryObjects,
324            QueryFunction::QueryServerInformation,
325        ];
326        let request = RequestPayload::Query(wanted_info);
327
328        // Execute the request and capture the response
329        let response = self.do_request(request).await?;
330
331        // Process the successful response
332        get_response_payload_for_type!(response, ResponsePayload::Query)
333    }
334
335    /// Serialize a KMIP 1.0 [Create Key Pair](https://docs.oasis-open.org/kmip/spec/v1.0/os/kmip-spec-1.0-os.html#_Toc262581269) request to create an RSA key pair.
336    ///
337    /// See also: [do_request()](Self::do_request())
338    ///
339    /// Creates an RSA key pair.
340    ///
341    /// To create keys of other types or with other parameters you must compose the Create Key Pair request manually
342    /// and pass it to [do_request()](Self::do_request()) directly.
343    #[maybe_async::maybe_async]
344    pub async fn create_rsa_key_pair(
345        &self,
346        key_length: i32,
347        private_key_name: String,
348        public_key_name: String,
349    ) -> Result<(String, String)> {
350        // Setup the request
351        let request = RequestPayload::CreateKeyPair(
352            Some(CommonTemplateAttribute::unnamed(vec![
353                request::Attribute::CryptographicAlgorithm(CryptographicAlgorithm::RSA),
354                request::Attribute::CryptographicLength(key_length),
355            ])),
356            Some(PrivateKeyTemplateAttribute::unnamed(vec![
357                request::Attribute::Name(private_key_name),
358                request::Attribute::CryptographicUsageMask(CryptographicUsageMask::Sign),
359            ])),
360            Some(PublicKeyTemplateAttribute::unnamed(vec![
361                request::Attribute::Name(public_key_name),
362                request::Attribute::CryptographicUsageMask(CryptographicUsageMask::Verify),
363            ])),
364        );
365
366        // Execute the request and capture the response
367        let response = self.do_request(request).await?;
368
369        // Process the successful response
370        get_response_payload_for_type!(response, ResponsePayload::CreateKeyPair).map(|payload| {
371            (
372                payload.private_key_unique_identifier.deref().clone(),
373                payload.public_key_unique_identifier.deref().clone(),
374            )
375        })
376    }
377
378    /// Serialize a KMIP 1.2 [Rng Retrieve](https://docs.oasis-open.org/kmip/spec/v1.2/os/kmip-spec-v1.2-os.html#_Toc409613562)
379    /// operation to retrieve a number of random bytes.
380    ///
381    /// See also: [do_request()](Self::do_request())
382    ///
383    #[maybe_async::maybe_async]
384    pub async fn rng_retrieve(&self, num_bytes: i32) -> Result<RNGRetrieveResponsePayload> {
385        let request = RequestPayload::RNGRetrieve(DataLength(num_bytes));
386
387        // Execute the request and capture the response
388        let response = self.do_request(request).await?;
389
390        // Process the successful response
391        get_response_payload_for_type!(response, ResponsePayload::RNGRetrieve)
392    }
393
394    /// Serialize a KMIP 1.2 [Sign](https://docs.oasis-open.org/kmip/spec/v1.2/os/kmip-spec-v1.2-os.html#_Toc409613558)
395    /// operation to sign the given bytes with the given private key ID.
396    ///
397    /// See also: [do_request()](Self::do_request())
398    ///
399    #[maybe_async::maybe_async]
400    pub async fn sign(&self, private_key_id: &str, in_bytes: &[u8]) -> Result<SignResponsePayload> {
401        let request = RequestPayload::Sign(
402            Some(UniqueIdentifier(private_key_id.to_owned())),
403            Some(
404                CryptographicParameters::default()
405                    .with_padding_method(PaddingMethod::PKCS1_v1_5)
406                    .with_hashing_algorithm(HashingAlgorithm::SHA256)
407                    .with_cryptographic_algorithm(CryptographicAlgorithm::RSA),
408            ),
409            Data(in_bytes.to_vec()),
410        );
411
412        // Execute the request and capture the response
413        let response = self.do_request(request).await?;
414
415        get_response_payload_for_type!(response, ResponsePayload::Sign)
416    }
417
418    /// Serialize a KMIP 1.0 [Activate](https://docs.oasis-open.org/kmip/spec/v1.0/os/kmip-spec-1.0-os.html#_Toc262581226)
419    /// operation to activate a given private key ID.
420    ///
421    /// See also: [do_request()](Self::do_request())
422    ///
423    /// To activate other kinds of managed object you must compose the Activate request manually and pass it to
424    /// [do_request()](Self::do_request()) directly.
425    #[maybe_async::maybe_async]
426    pub async fn activate_key(&self, private_key_id: &str) -> Result<()> {
427        let request = RequestPayload::Activate(Some(UniqueIdentifier(private_key_id.to_owned())));
428
429        // Execute the request and capture the response
430        let response = self.do_request(request).await?;
431
432        // Process the successful response
433        get_response_payload_for_type!(response, ResponsePayload::Activate).map(|_| ())
434    }
435
436    /// Serialize a KMIP 1.0 [Revoke](https://docs.oasis-open.org/kmip/spec/v1.0/os/kmip-spec-1.0-os.html#_Toc262581227)
437    /// operation to deactivate a given private key ID.
438    ///
439    /// See also: [do_request()](Self::do_request())
440    ///
441    /// To deactivate other kinds of managed object you must compose the Revoke request manually and pass it to
442    /// [do_request()](Self::do_request()) directly.
443    #[maybe_async::maybe_async]
444    pub async fn revoke_key(&self, private_key_id: &str) -> Result<()> {
445        let request = RequestPayload::Revoke(
446            Some(UniqueIdentifier(private_key_id.to_owned())),
447            RevocationReason(
448                RevocationReasonCode::CessationOfOperation,
449                Option::<RevocationMessage>::None,
450            ),
451            Option::<CompromiseOccurrenceDate>::None,
452        );
453
454        // Execute the request and capture the response
455        let response = self.do_request(request).await?;
456
457        // Process the successful response
458        get_response_payload_for_type!(response, ResponsePayload::Revoke).map(|_| ())
459    }
460
461    /// Serialize a KMIP 1.0 [Destroy](https://docs.oasis-open.org/kmip/spec/v1.0/os/kmip-spec-1.0-os.html#_Toc262581228)
462    /// operation to destroy a given private key ID.
463    ///
464    /// See also: [do_request()](Self::do_request())
465    ///
466    /// To destroy other kinds of managed object you must compose the Destroy request manually and pass it to
467    /// [do_request()](Self::do_request()) directly.
468    #[maybe_async::maybe_async]
469    pub async fn destroy_key(&self, key_id: &str) -> Result<()> {
470        let request = RequestPayload::Destroy(Some(UniqueIdentifier(key_id.to_owned())));
471
472        // Execute the request and capture the response
473        let response = self.do_request(request).await?;
474
475        // Process the successful response
476        get_response_payload_for_type!(response, ResponsePayload::Destroy).map(|_| ())
477    }
478
479    /// Serialize a KMIP 1.0 [ModifyAttribute](http://docs.oasis-open.org/kmip/spec/v1.0/os/kmip-spec-1.0-os.html#_Toc262581222)
480    /// operation to rename a given key ID.
481    ///
482    /// See also: [do_request()](Self::do_request())
483    ///
484    /// To modify other attributes of managed objects you must compose the Modify Attribute request manually and pass
485    /// it to [do_request()](Self::do_request()) directly.
486    #[maybe_async::maybe_async]
487    pub async fn rename_key(&self, key_id: &str, new_name: String) -> Result<ModifyAttributeResponsePayload> {
488        // Setup the request
489        let request = RequestPayload::ModifyAttribute(
490            Some(UniqueIdentifier(key_id.to_string())),
491            request::Attribute::Name(new_name),
492        );
493
494        // Execute the request and capture the response
495        let response = self.do_request(request).await?;
496
497        // Process the successful response
498        get_response_payload_for_type!(response, ResponsePayload::ModifyAttribute)
499    }
500
501    /// Serialize a KMIP 1.0 [Get](http://docs.oasis-open.org/kmip/spec/v1.0/os/kmip-spec-1.0-os.html#_Toc262581218)
502    /// operation to get the details of a given key ID.
503    ///
504    /// See also: [do_request()](Self::do_request())
505    #[maybe_async::maybe_async]
506    pub async fn get_key(&self, key_id: &str) -> Result<GetResponsePayload> {
507        // Setup the request
508        let request = RequestPayload::Get(
509            Some(UniqueIdentifier(key_id.to_string())),
510            Option::<KeyFormatType>::None,
511            Option::<KeyCompressionType>::None,
512            Option::<KeyWrappingSpecification>::None,
513        );
514
515        // Execute the request and capture the response
516        let response = self.do_request(request).await?;
517
518        // Process the successful response
519        get_response_payload_for_type!(response, ResponsePayload::Get)
520    }
521}
522
523impl<T> Clone for Client<T> {
524    fn clone(&self) -> Self {
525        Self {
526            username: self.username.clone(),
527            password: self.password.clone(),
528            stream: self.stream.clone(),
529            reader_config: self.reader_config.clone(),
530            connection_error_count: AtomicU8::new(self.connection_error_count()),
531            last_req_diag_str: self.last_req_diag_str.clone(),
532            last_res_diag_str: self.last_res_diag_str.clone(),
533            pretty_printer: self.pretty_printer.clone(),
534        }
535    }
536}
537
538impl<T> Client<T> {
539    fn auth(&self) -> Option<CredentialType> {
540        if self.username.is_some() && self.password.is_some() {
541            Some(CredentialType::UsernameAndPassword(
542                auth::UsernameAndPasswordCredential::new(self.username.clone().unwrap(), self.password.clone()),
543            ))
544        } else {
545            None
546        }
547    }
548
549    /// Get a clone of the client's last req diag str.
550    pub fn last_req_diag_str(&self) -> Option<String> {
551        self.last_req_diag_str.borrow().to_owned()
552    }
553
554    /// Get a clone of the client's last res diag str.
555    pub fn last_res_diag_str(&self) -> Option<String> {
556        self.last_res_diag_str.borrow().to_owned()
557    }
558
559    /// Get the count of connection errors experienced by this Client.
560    pub fn connection_error_count(&self) -> u8 {
561        self.connection_error_count.load(Ordering::SeqCst)
562    }
563}
564
565#[cfg(test)]
566mod test {
567    use std::{
568        io::{Cursor, Read, Write},
569        net::TcpStream,
570    };
571
572    use kmip_ttlv::Config;
573
574    #[cfg(feature = "tls-with-openssl")]
575    use openssl::ssl::{SslConnector, SslFiletype, SslMethod, SslVerifyMode};
576
577    use crate::{
578        client::ClientBuilder,
579        types::{
580            request::{QueryFunction, RequestPayload},
581            response::ResponsePayload,
582        },
583    };
584
585    struct MockStream {
586        pub response: Cursor<Vec<u8>>,
587    }
588
589    impl Write for MockStream {
590        fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
591            std::io::sink().write(buf)
592        }
593
594        fn flush(&mut self) -> std::io::Result<()> {
595            Ok(())
596        }
597    }
598
599    impl Read for MockStream {
600        fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
601            self.response.read(buf)
602        }
603    }
604
605    #[test]
606    fn test_query() {
607        let response_hex = concat!(
608            "42007B010000023042007A0100000048420069010000002042006A0200000004000000010000000042006B02000000040",
609            "0000000000000004200920900000008000000004B7918AA42000D0200000004000000010000000042000F01000001D842",
610            "005C0500000004000000180000000042007F0500000004000000000000000042007C01000001B042005C0500000004000",
611            "000010000000042005C0500000004000000020000000042005C0500000004000000030000000042005C05000000040000",
612            "00040000000042005C0500000004000000080000000042005C0500000004000000090000000042005C050000000400000",
613            "00A0000000042005C05000000040000000B0000000042005C05000000040000000C0000000042005C0500000004000000",
614            "0D0000000042005C05000000040000000E0000000042005C05000000040000000F0000000042005C05000000040000001",
615            "00000000042005C0500000004000000110000000042005C0500000004000000120000000042005C050000000400000013",
616            "0000000042005C0500000004000000140000000042005C0500000004000000150000000042005C0500000004000000160",
617            "000000042005C0500000004000000180000000042005C0500000004000000190000000042005C05000000040000001A00",
618            "0000004200570500000004000000010000000042005705000000040000000200000000420057050000000400000003000",
619            "000004200570500000004000000040000000042005705000000040000000600000000"
620        );
621        let response_bytes = hex::decode(response_hex).unwrap();
622
623        let mut stream = MockStream {
624            response: Cursor::new(response_bytes),
625        };
626
627        let client = ClientBuilder::new(&mut stream).build();
628
629        let response_payload = client.query().unwrap();
630
631        dbg!(response_payload);
632    }
633
634    #[test]
635    fn test_create_rsa_key_pair() {
636        let response_hex = concat!(
637            "42007B01000000E042007A0100000048420069010000002042006A0200000004000000010000000042006B02000000040",
638            "0000000000000004200920900000008000000004B73C13A42000D0200000004000000010000000042000F010000008842",
639            "005C0500000004000000020000000042007F0500000004000000000000000042007C01000000604200940700000024383",
640            "93566373263322D623230612D343964382D393530342D3664633231313563633034320000000042009407000000246132",
641            "3432666361342D656266302D343339382D616336352D38373962616234393032353900000000"
642        );
643        let response_bytes = hex::decode(response_hex).unwrap();
644
645        let mut stream = MockStream {
646            response: Cursor::new(response_bytes),
647        };
648
649        let client = ClientBuilder::new(&mut stream).build();
650
651        let response_payload = client
652            .create_rsa_key_pair(1024, "My Private Key".into(), "My Public Key".into())
653            .unwrap();
654
655        dbg!(response_payload);
656    }
657
658    #[cfg(feature = "tls-with-openssl")]
659    #[test]
660    #[ignore = "Requires a running PyKMIP instance"]
661    fn test_pykmip_query_against_server_with_openssl() {
662        let mut connector = SslConnector::builder(SslMethod::tls()).unwrap();
663        connector.set_verify(SslVerifyMode::NONE);
664        connector
665            .set_certificate_file("/etc/pykmip/server.crt", SslFiletype::PEM)
666            .unwrap();
667        connector
668            .set_private_key_file("/etc/pykmip/server.key", SslFiletype::PEM)
669            .unwrap();
670        let connector = connector.build();
671        let stream = TcpStream::connect("localhost:5696").unwrap();
672        let mut tls = connector.connect("localhost", stream).unwrap();
673
674        let client = ClientBuilder::new(&mut tls)
675            .with_reader_config(Config::default().with_max_bytes(64 * 1024))
676            .build();
677
678        let response_payload = client.query().unwrap();
679
680        dbg!(response_payload);
681    }
682
683    #[cfg(feature = "tls-with-rustls")]
684    #[test]
685    #[ignore = "Requires a running PyKMIP instance"]
686    fn test_pykmip_query_against_server_with_rustls() {
687        // To setup input files for PyKMIP and RustLS to work together we must use a cipher they have in common, either
688        // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 or TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA384.
689        //
690        // To generate the required files use the following commands:
691        //
692        // ```
693        // # Prepare a directory to contain the PyKMIP config file and supporting certificate files
694        // sudo mkdir /etc/pykmip
695        // sudo chown $USER: /etc/pykmip
696        // cd /etc/pykmip
697        //
698        // # Prepare an OpenSSL configuration file for adding a Subject Alternative Name (SAN) to the generated CSR
699        // # and certificate. Without the SAN we would need to use the RustDL "dangerous" feature to ignore the server/
700        // # certificate mismatched name verification failure.
701        // cat <<EOF >san.cnf
702        // [ext]
703        // subjectAltName = DNS:localhost
704        // EOF
705        //
706        // # Prepare to do CA signing
707        // mkdir demoCA
708        // touch demoCA/index.txt
709        // echo 01 > demoCA/serial
710        //
711        // # Generate CA key
712        // # Warns: using curve name prime256v1 instead of secp256r1
713        // openssl ecparam -out ca.key -name secp256r1 -genkey
714        //
715        // # Generate CA certificate
716        // openssl req -x509 -new -key ca.key -out ca.crt -outform PEM -days 3650 -subj "/C=NL/ST=Noord Holland/L=Amsterdam/O=NLnet Labs/CN=localhost"
717        //
718        // # Generate PyKMIP server key
719        // # Warns: using curve name prime256v1 instead of secp256r1
720        // openssl ecparam -out server.key -name secp256r1 -genkey
721        //
722        // # Generate request for PyKMIP server certificate
723        // openssl req -new -nodes -key server.key -outform pem -out server.csr -subj "/C=NL/ST=Noord Holland/L=Amsterdam/O=NLnet Labs/CN=localhost"
724        //
725        // # Ask the CA to sign the request to create the PyKMIP server certificate
726        // openssl ca -keyfile ca.key -cert ca.crt -in server.csr -out server.crt -outdir . -batch -noemailDN -extfile san.cnf -extensions ext
727        //
728        // # Convert the server key from --BEGIN EC PRIVATE KEY-- format to --BEGIN PRIVATE KEY-- format
729        // # as RustLS cannot pass the former as a client certificate when connecting...
730        // openssl pkcs8 -topk8 -nocrypt -in server.key -out server.pkcs8.key
731        //
732        // # Replace the original server.key with the PKCS#8 format one because PyKMIP can use that as well
733        // mv server.pkcs8.key server.key
734        //
735        // # Now write a PyKMIP config file that uses the generated files
736        // cat <<EOF >server.conf
737        // [server]
738        // hostname=127.0.0.1
739        // port=5696
740        // certificate_path=/etc/pykmip/server.crt
741        // key_path=/etc/pykmip/server.key
742        // ca_path=/etc/pykmip/ca.crt
743        // auth_suite=TLS1.2
744        // enable_tls_client_auth=False
745        // tls_cipher_suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
746        // logging_level=DEBUG
747        // database_path=/tmp/pykmip.db
748        // EOF
749        //
750        // # Lastly, run PyKMIP:
751        // pykmip-server
752        // ```
753
754        // For more insight into what RustLS is doing enabling the "logging" feature of the RustLS crate and then use
755        // a logging implementation here, e.g.
756        //     stderrlog::new()
757        //         .module(module_path!())
758        //         .module("rustls")
759        //         .quiet(false)
760        //         .verbosity(5) // show INFO level logging by default, use -q to silence this
761        //         .timestamp(stderrlog::Timestamp::Second)
762        //         .init()
763        //         .unwrap();
764
765        fn load_binary_file(path: &'static str) -> std::io::Result<Vec<u8>> {
766            let mut buf = Vec::new();
767            std::fs::File::open(path)?.read_to_end(&mut buf)?;
768            Ok(buf)
769        }
770
771        fn bytes_to_cert_chain(bytes: &[u8]) -> std::io::Result<Vec<rustls::Certificate>> {
772            let cert_chain = rustls_pemfile::read_all(&mut BufReader::new(bytes))?
773                .iter()
774                .map(|i: &rustls_pemfile::Item| match i {
775                    rustls_pemfile::Item::X509Certificate(bytes) => rustls::Certificate(bytes.clone()),
776                    rustls_pemfile::Item::RSAKey(_) => panic!("Expected an X509 certificate, got an RSA key"),
777                    rustls_pemfile::Item::PKCS8Key(_) => panic!("Expected an X509 certificate, got a PKCS8 key"),
778                })
779                .collect();
780            Ok(cert_chain)
781        }
782
783        fn bytes_to_private_key(bytes: &[u8]) -> std::io::Result<rustls::PrivateKey> {
784            let private_key = rustls_pemfile::read_one(&mut BufReader::new(bytes))?
785                .map(|i: rustls_pemfile::Item| match i {
786                    rustls_pemfile::Item::X509Certificate(_) => panic!("Expected a PKCS8 key, got an X509 certificate"),
787                    rustls_pemfile::Item::RSAKey(_) => panic!("Expected a PKCS8 key, got an RSA key"),
788                    rustls_pemfile::Item::PKCS8Key(bytes) => rustls::PrivateKey(bytes.clone()),
789                })
790                .unwrap();
791            Ok(private_key)
792        }
793
794        // Load files
795        let ca_cert_pem = load_binary_file("/etc/pykmip/ca.crt").unwrap();
796        let server_cert_pem = load_binary_file("/etc/pykmip/server.crt").unwrap();
797        let server_key_pem = load_binary_file("/etc/pykmip/server.key").unwrap();
798
799        let mut config = rustls::ClientConfig::new();
800        config
801            .root_store
802            .add_pem_file(&mut BufReader::new(ca_cert_pem.as_slice()))
803            .unwrap();
804        config
805            .root_store
806            .add_pem_file(&mut BufReader::new(server_cert_pem.as_slice()))
807            .unwrap();
808
809        let cert_chain = bytes_to_cert_chain(&server_cert_pem).unwrap();
810        let key_der = bytes_to_private_key(&server_key_pem).unwrap();
811        config.set_single_client_cert(cert_chain, key_der).unwrap();
812
813        let rc_config = Arc::new(config);
814        let localhost = webpki::DNSNameRef::try_from_ascii_str("localhost").unwrap();
815        let mut sess = rustls::ClientSession::new(&rc_config, localhost);
816        let mut stream = TcpStream::connect("localhost:5696").unwrap();
817        let mut tls = rustls::Stream::new(&mut sess, &mut stream);
818
819        let client = ClientBuilder::new(&mut tls)
820            .with_reader_config(Config::default().with_max_bytes(64 * 1024))
821            .build();
822
823        let response_payload = client.query().unwrap();
824
825        dbg!(response_payload);
826    }
827
828    #[test]
829    #[ignore = "Requires a running Kryptus instance"]
830    fn test_kryptus_query_against_server() {
831        let mut connector = SslConnector::builder(SslMethod::tls()).unwrap();
832        connector.set_verify(SslVerifyMode::NONE);
833        let connector = connector.build();
834        let host = std::env::var("KRYPTUS_HOST").unwrap();
835        let port = std::env::var("KRYPTUS_PORT").unwrap();
836        let stream = TcpStream::connect(format!("{}:{}", host, port)).unwrap();
837        let mut tls = connector.connect(&host, stream).unwrap();
838
839        let client = ClientBuilder::new(&mut tls)
840            .with_credentials(
841                std::env::var("KRYPTUS_USER").unwrap(),
842                Some(std::env::var("KRYPTUS_PASS").unwrap()),
843            )
844            .with_reader_config(Config::default().with_max_bytes(64 * 1024))
845            .build();
846
847        let response_payload = client.query().unwrap();
848
849        dbg!(response_payload);
850    }
851
852    #[test]
853    fn test_pykmip_query_response() {
854        let response_hex = concat!(
855            "42007b010000014042007a0100000048420069010000002042006a0200000004000000010000000042006b02000000040",
856            "00000000000000042009209000000080000000060ff457142000d0200000004000000010000000042000f01000000e842",
857            "005c0500000004000000180000000042007f0500000004000000000000000042007c01000000c042005c0500000004000",
858            "000010000000042005c0500000004000000020000000042005c0500000004000000030000000042005c05000000040000",
859            "00050000000042005c0500000004000000080000000042005c05000000040000000a0000000042005c050000000400000",
860            "00b0000000042005c05000000040000000c0000000042005c0500000004000000120000000042005c0500000004000000",
861            "130000000042005c0500000004000000140000000042005c05000000040000001800000000"
862        );
863        let response_bytes = hex::decode(response_hex).unwrap();
864
865        let mut stream = MockStream {
866            response: Cursor::new(response_bytes),
867        };
868
869        let client = ClientBuilder::new(&mut stream).build();
870
871        let result = client
872            .do_request(RequestPayload::Query(vec![QueryFunction::QueryOperations]))
873            .unwrap();
874
875        if let ResponsePayload::Query(payload) = result {
876            dbg!(payload);
877        } else {
878            panic!("Expected query response!");
879        }
880    }
881}