| 
 | 1 | +// Copyright 2020 Contributors to the Parsec project.  | 
 | 2 | +// SPDX-License-Identifier: Apache-2.0  | 
 | 3 | +//! Unix peer credentials authenticator  | 
 | 4 | +//!  | 
 | 5 | +//! The `UnixPeerCredentialsAuthenticator` uses Unix peer credentials to perform authentication. As  | 
 | 6 | +//! such, it uses the effective Unix user ID (UID) to authenticate the connecting process. Unix  | 
 | 7 | +//! peer credentials also allow us to access the effective Unix group ID (GID) of the connecting  | 
 | 8 | +//! process, although this information is currently unused.  | 
 | 9 | +//!  | 
 | 10 | +//! Currently, the stringified UID is used as the application name.  | 
 | 11 | +
  | 
 | 12 | +use super::ApplicationName;  | 
 | 13 | +use super::Authenticate;  | 
 | 14 | +use crate::front::listener::ConnectionMetadata;  | 
 | 15 | +use log::error;  | 
 | 16 | +use parsec_interface::operations::list_authenticators;  | 
 | 17 | +use parsec_interface::requests::request::RequestAuth;  | 
 | 18 | +use parsec_interface::requests::AuthType;  | 
 | 19 | +use parsec_interface::requests::{ResponseStatus, Result};  | 
 | 20 | +use parsec_interface::secrecy::ExposeSecret;  | 
 | 21 | +use std::convert::TryInto;  | 
 | 22 | + | 
 | 23 | +/// Unix peer credentials authenticator.  | 
 | 24 | +#[derive(Copy, Clone, Debug)]  | 
 | 25 | +pub struct UnixPeerCredentialsAuthenticator;  | 
 | 26 | + | 
 | 27 | +impl Authenticate for UnixPeerCredentialsAuthenticator {  | 
 | 28 | +    fn describe(&self) -> Result<list_authenticators::AuthenticatorInfo> {  | 
 | 29 | +        Ok(list_authenticators::AuthenticatorInfo {  | 
 | 30 | +            description: String::from(  | 
 | 31 | +                "Uses Unix peer credentials to authenticate the client. Verifies that the self-declared \  | 
 | 32 | +                Unix user identifier (UID) in the request's authentication header matches that which is \  | 
 | 33 | +                found from the peer credentials."  | 
 | 34 | +            ),  | 
 | 35 | +            version_maj: 0,  | 
 | 36 | +            version_min: 1,  | 
 | 37 | +            version_rev: 0,  | 
 | 38 | +            id: AuthType::PeerCredentials,  | 
 | 39 | +        })  | 
 | 40 | +    }  | 
 | 41 | + | 
 | 42 | +    fn authenticate(  | 
 | 43 | +        &self,  | 
 | 44 | +        auth: &RequestAuth,  | 
 | 45 | +        meta: Option<ConnectionMetadata>,  | 
 | 46 | +    ) -> Result<ApplicationName> {  | 
 | 47 | +        // Parse authentication request.  | 
 | 48 | +        let expected_uid_bytes = auth.buffer.expose_secret();  | 
 | 49 | +        if expected_uid_bytes.is_empty() {  | 
 | 50 | +            error!("Expected UID in authentication request, but it is empty.");  | 
 | 51 | +            return Err(ResponseStatus::AuthenticationError);  | 
 | 52 | +        }  | 
 | 53 | + | 
 | 54 | +        const EXPECTED_UID_SIZE_BYTES: usize = 4;  | 
 | 55 | +        let expected_uid: [u8; 4] = expected_uid_bytes.as_slice().try_into().map_err(|_| {  | 
 | 56 | +            error!(  | 
 | 57 | +                "UID in authentication request is not the right size (expected: {}, got: {}).",  | 
 | 58 | +                EXPECTED_UID_SIZE_BYTES,  | 
 | 59 | +                expected_uid_bytes.len()  | 
 | 60 | +            );  | 
 | 61 | +            ResponseStatus::AuthenticationError  | 
 | 62 | +        })?;  | 
 | 63 | +        let expected_uid = u32::from_le_bytes(expected_uid);  | 
 | 64 | + | 
 | 65 | +        let meta = meta.ok_or_else(|| {  | 
 | 66 | +            error!("Authenticator did not receive any metadata; cannot perform authentication.");  | 
 | 67 | +            ResponseStatus::AuthenticationError  | 
 | 68 | +        })?;  | 
 | 69 | + | 
 | 70 | +        let (uid, _gid) = match meta {  | 
 | 71 | +            ConnectionMetadata::UnixPeerCredentials { uid, gid } => (uid, gid),  | 
 | 72 | +            // TODO: add wildcard pattern when `ConnectionMetadata` has more possibilities.  | 
 | 73 | +        };  | 
 | 74 | + | 
 | 75 | +        // Authentication is successful if the _actual_ UID from the Unix peer credentials equals  | 
 | 76 | +        // the self-declared UID in the authentication request.  | 
 | 77 | +        if uid == expected_uid {  | 
 | 78 | +            Ok(ApplicationName(uid.to_string()))  | 
 | 79 | +        } else {  | 
 | 80 | +            error!("Declared UID in authentication request does not match the process's UID.");  | 
 | 81 | +            Err(ResponseStatus::AuthenticationError)  | 
 | 82 | +        }  | 
 | 83 | +    }  | 
 | 84 | +}  | 
 | 85 | + | 
 | 86 | +#[cfg(test)]  | 
 | 87 | +mod test {  | 
 | 88 | +    use super::super::Authenticate;  | 
 | 89 | +    use super::UnixPeerCredentialsAuthenticator;  | 
 | 90 | +    use crate::front::listener::ConnectionMetadata;  | 
 | 91 | +    use parsec_interface::requests::request::RequestAuth;  | 
 | 92 | +    use parsec_interface::requests::ResponseStatus;  | 
 | 93 | +    use rand::Rng;  | 
 | 94 | +    use std::os::unix::net::UnixStream;  | 
 | 95 | +    use users::get_current_uid;  | 
 | 96 | + | 
 | 97 | +    #[test]  | 
 | 98 | +    fn successful_authentication() {  | 
 | 99 | +        // This test should PASS; we are verifying that our username gets set as the application  | 
 | 100 | +        // secret when using Unix peer credentials authentication with Unix domain sockets.  | 
 | 101 | + | 
 | 102 | +        // Create two connected sockets.  | 
 | 103 | +        let (sock_a, _sock_b) = UnixStream::pair().unwrap();  | 
 | 104 | +        let (cred_a, _cred_b) = (sock_a.peer_cred().unwrap(), _sock_b.peer_cred().unwrap());  | 
 | 105 | + | 
 | 106 | +        let authenticator = UnixPeerCredentialsAuthenticator {};  | 
 | 107 | + | 
 | 108 | +        let req_auth_data = cred_a.uid.to_le_bytes().to_vec();  | 
 | 109 | +        let req_auth = RequestAuth::new(req_auth_data);  | 
 | 110 | +        let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials {  | 
 | 111 | +            uid: cred_a.uid,  | 
 | 112 | +            gid: cred_a.gid,  | 
 | 113 | +        });  | 
 | 114 | + | 
 | 115 | +        let auth_name = authenticator  | 
 | 116 | +            .authenticate(&req_auth, conn_metadata)  | 
 | 117 | +            .expect("Failed to authenticate");  | 
 | 118 | + | 
 | 119 | +        assert_eq!(auth_name.get_name(), get_current_uid().to_string());  | 
 | 120 | +    }  | 
 | 121 | + | 
 | 122 | +    #[test]  | 
 | 123 | +    fn unsuccessful_authentication_wrong_declared_uid() {  | 
 | 124 | +        // This test should FAIL; we are trying to authenticate, but we are declaring the wrong  | 
 | 125 | +        // UID.  | 
 | 126 | + | 
 | 127 | +        // Create two connected sockets.  | 
 | 128 | +        let (sock_a, _sock_b) = UnixStream::pair().unwrap();  | 
 | 129 | +        let (cred_a, _cred_b) = (sock_a.peer_cred().unwrap(), _sock_b.peer_cred().unwrap());  | 
 | 130 | + | 
 | 131 | +        let authenticator = UnixPeerCredentialsAuthenticator {};  | 
 | 132 | + | 
 | 133 | +        let wrong_uid = cred_a.uid + 1;  | 
 | 134 | +        let wrong_req_auth_data = wrong_uid.to_le_bytes().to_vec();  | 
 | 135 | +        let req_auth = RequestAuth::new(wrong_req_auth_data);  | 
 | 136 | +        let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials {  | 
 | 137 | +            uid: cred_a.uid,  | 
 | 138 | +            gid: cred_a.gid,  | 
 | 139 | +        });  | 
 | 140 | + | 
 | 141 | +        let auth_result = authenticator.authenticate(&req_auth, conn_metadata);  | 
 | 142 | +        assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));  | 
 | 143 | +    }  | 
 | 144 | + | 
 | 145 | +    #[test]  | 
 | 146 | +    fn unsuccessful_authentication_garbage_data() {  | 
 | 147 | +        // This test should FAIL; we are sending garbage (random) data in the request.  | 
 | 148 | + | 
 | 149 | +        // Create two connected sockets.  | 
 | 150 | +        let (sock_a, _sock_b) = UnixStream::pair().unwrap();  | 
 | 151 | +        let (cred_a, _cred_b) = (sock_a.peer_cred().unwrap(), _sock_b.peer_cred().unwrap());  | 
 | 152 | + | 
 | 153 | +        let authenticator = UnixPeerCredentialsAuthenticator {};  | 
 | 154 | + | 
 | 155 | +        let garbage_data = rand::thread_rng().gen::<[u8; 32]>().to_vec();  | 
 | 156 | +        let req_auth = RequestAuth::new(garbage_data);  | 
 | 157 | +        let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials {  | 
 | 158 | +            uid: cred_a.uid,  | 
 | 159 | +            gid: cred_a.gid,  | 
 | 160 | +        });  | 
 | 161 | + | 
 | 162 | +        let auth_result = authenticator.authenticate(&req_auth, conn_metadata);  | 
 | 163 | +        assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));  | 
 | 164 | +    }  | 
 | 165 | + | 
 | 166 | +    #[test]  | 
 | 167 | +    fn unsuccessful_authentication_no_metadata() {  | 
 | 168 | +        let authenticator = UnixPeerCredentialsAuthenticator {};  | 
 | 169 | +        let req_auth = RequestAuth::new("secret".into());  | 
 | 170 | + | 
 | 171 | +        let conn_metadata = None;  | 
 | 172 | +        let auth_result = authenticator.authenticate(&req_auth, conn_metadata);  | 
 | 173 | +        assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));  | 
 | 174 | +    }  | 
 | 175 | + | 
 | 176 | +    #[test]  | 
 | 177 | +    fn unsuccessful_authentication_wrong_metadata() {  | 
 | 178 | +        // TODO: this test needs implementing when we have more than one metadata type. At the  | 
 | 179 | +        // moment, the compiler just complains with an 'unreachable branch' message.  | 
 | 180 | +    }  | 
 | 181 | +}  | 
0 commit comments