Skip to content

Commit 8b4bfb6

Browse files
author
Joe Ellis
committed
Add Unix peer credentials authenticator
This authenticator uses Unix peer credentials for authentication. Unix peer credentials provide direct access to the (effective) uid/gid on the other end of a domain socket connect, without cooperation between the endpoints. This means that we can determine the uid/gid of the connecting process, and therefore infer the username of the user that owns said process. This authenticator: - grabs the (uid, gid) pair of the connecting process. - grabs the self-declared uid sent in the authentication request. - verifies that authentication is successful by checking that the self-declared uid in the authentication request is equal to the actual uid from the peer credentials. - if authentication was successful, creates an `ApplicationName` based on the uid. The authenticator is hidden behind the Cargo feature `uds-authenticator`. Note that this patch depends on the following PR being merged: rust-lang/rust#75148 At the time of writing, this PR is currently under review and is not merged into the Rust stdlib. This patch therefore will not build with the current a stable/nightly compiler. Signed-off-by: Joe Ellis <[email protected]>
1 parent 9530d64 commit 8b4bfb6

File tree

6 files changed

+195
-7
lines changed

6 files changed

+195
-7
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ mbed-crypto-provider = ["psa-crypto"]
6969
pkcs11-provider = ["pkcs11", "picky-asn1-der", "picky-asn1", "picky-asn1-x509"]
7070
tpm-provider = ["tss-esapi", "picky-asn1-der", "picky-asn1", "picky-asn1-x509"]
7171
all-providers = ["tpm-provider", "pkcs11-provider", "mbed-crypto-provider"]
72+
uds-authenticator = []
7273
# The Mbed provider is not included in the docs because of 2 reasons:
7374
# 1) it is currently impossible for it to be built inside the docs.rs build system (as it has dependencies
7475
# that cannot be fulfilled)

src/authenticators/mod.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@
88
//! used throughout the service for identifying the request initiator. The input to an authentication
99
//! is the `RequestAuth` field of a request, which is parsed by the authenticator specified in the header.
1010
//! The authentication functionality is abstracted through an `Authenticate` trait.
11-
//!
12-
//! Currently only a simple Direct Authenticator component is implemented.
1311
1412
pub mod direct_authenticator;
1513

14+
#[cfg(feature = "uds-authenticator")]
15+
pub mod uds_authenticator;
16+
1617
use crate::front::listener::ConnectionMetadata;
1718
use parsec_interface::requests::request::RequestAuth;
1819
use parsec_interface::requests::Result;
+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// Copyright 2020 Contributors to the Parsec project.
2+
// SPDX-License-Identifier: Apache-2.0
3+
//! Unix domain socket (UDS) peer credentials authenticator
4+
//!
5+
//! The `UDSAuthenticator` uses peer credentials from Unix domain sockets to perform
6+
//! authentication. As such, it uses the UID/GID to grab a username for the owner of the connecting
7+
//! process. This is used as the application name.
8+
9+
use super::ApplicationName;
10+
use super::Authenticate;
11+
use crate::front::listener::ConnectionMetadata;
12+
use log::error;
13+
use parsec_interface::requests::request::RequestAuth;
14+
use parsec_interface::requests::{ResponseStatus, Result};
15+
use parsec_interface::secrecy::ExposeSecret;
16+
use std::str;
17+
18+
#[derive(Copy, Clone, Debug)]
19+
pub struct UDSAuthenticator;
20+
21+
impl Authenticate for UDSAuthenticator {
22+
fn authenticate(
23+
&self,
24+
auth: &RequestAuth,
25+
meta: Option<ConnectionMetadata>,
26+
) -> Result<ApplicationName> {
27+
// Parse authentication request.
28+
let expected_uid = auth.buffer.expose_secret();
29+
if expected_uid.is_empty() {
30+
error!("Expected UID in authentication request, but it is empty.");
31+
return Err(ResponseStatus::AuthenticationError);
32+
}
33+
34+
let expected_uid = match str::from_utf8(expected_uid) {
35+
Ok(expected_uid) => expected_uid,
36+
Err(_) => {
37+
error!("Error parsing the authentication value as a UTF-8 string.");
38+
return Err(ResponseStatus::AuthenticationError);
39+
}
40+
};
41+
42+
// Get request metadata.
43+
let meta = match meta {
44+
Some(meta) => meta,
45+
None => {
46+
error!("Authenticator did not receive any metadata; cannot authenticate.");
47+
return Err(ResponseStatus::AuthenticationError);
48+
}
49+
};
50+
51+
let (uid, _gid) = match meta {
52+
ConnectionMetadata::PeerCredentials { uid, gid } => (uid, gid),
53+
// TODO: add wildcard pattern when `ConnectionMetadata` has more possibilities.
54+
};
55+
56+
let uid = uid.to_string();
57+
58+
// Authentication is successful if the _actual_ UID from the peer credentials equals the
59+
// self-declared UID in the authentication request.
60+
if uid == expected_uid {
61+
Ok(ApplicationName(uid))
62+
} else {
63+
error!("Declared UID in authentication request does not match the process's UID.");
64+
Err(ResponseStatus::AuthenticationError)
65+
}
66+
}
67+
}
68+
69+
#[cfg(test)]
70+
mod test {
71+
use super::super::Authenticate;
72+
use super::UDSAuthenticator;
73+
use crate::front::listener::ConnectionMetadata;
74+
use parsec_interface::requests::request::RequestAuth;
75+
use parsec_interface::requests::ResponseStatus;
76+
use rand::Rng;
77+
use std::os::unix::net::UnixStream;
78+
use users::get_current_uid;
79+
80+
#[test]
81+
fn successful_authentication() {
82+
// This test should PASS; we are verifying that our username gets set as the application
83+
// secret when using UDS authentication.
84+
85+
// Create two connected sockets.
86+
let (sock_a, _sock_b) = UnixStream::pair().unwrap();
87+
let (cred_a, _cred_b) = (sock_a.peer_cred().unwrap(), _sock_b.peer_cred().unwrap());
88+
89+
let authenticator = UDSAuthenticator {};
90+
91+
let req_auth_data = cred_a.uid.to_string().as_bytes().to_vec();
92+
let req_auth = RequestAuth::new(req_auth_data);
93+
let conn_metadata = Some(ConnectionMetadata::PeerCredentials {
94+
uid: cred_a.uid,
95+
gid: cred_a.gid,
96+
});
97+
98+
let auth_name = authenticator
99+
.authenticate(&req_auth, conn_metadata)
100+
.expect("Failed to authenticate");
101+
102+
assert_eq!(auth_name.get_name(), get_current_uid().to_string());
103+
}
104+
105+
#[test]
106+
fn unsuccessful_authentication_wrong_declared_uid() {
107+
// This test should FAIL; we are trying to authenticate, but we are declaring the wrong
108+
// UID.
109+
110+
// Create two connected sockets.
111+
let (sock_a, _sock_b) = UnixStream::pair().unwrap();
112+
let (cred_a, _cred_b) = (sock_a.peer_cred().unwrap(), _sock_b.peer_cred().unwrap());
113+
114+
let authenticator = UDSAuthenticator {};
115+
116+
let wrong_uid = cred_a.uid + 1;
117+
let wrong_req_auth_data = wrong_uid.to_string().as_bytes().to_vec();
118+
let req_auth = RequestAuth::new(wrong_req_auth_data);
119+
let conn_metadata = Some(ConnectionMetadata::PeerCredentials {
120+
uid: cred_a.uid,
121+
gid: cred_a.gid,
122+
});
123+
124+
let auth_result = authenticator.authenticate(&req_auth, conn_metadata);
125+
assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));
126+
}
127+
128+
#[test]
129+
fn unsuccessful_authentication_garbage_data() {
130+
// This test should FAIL; we are sending garbage (random) data in the request.
131+
132+
// Create two connected sockets.
133+
let (sock_a, _sock_b) = UnixStream::pair().unwrap();
134+
let (cred_a, _cred_b) = (sock_a.peer_cred().unwrap(), _sock_b.peer_cred().unwrap());
135+
136+
let authenticator = UDSAuthenticator {};
137+
138+
let garbage_data = rand::thread_rng().gen::<[u8; 32]>().to_vec();
139+
let req_auth = RequestAuth::new(garbage_data);
140+
let conn_metadata = Some(ConnectionMetadata::PeerCredentials {
141+
uid: cred_a.uid,
142+
gid: cred_a.gid,
143+
});
144+
145+
let auth_result = authenticator.authenticate(&req_auth, conn_metadata);
146+
assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));
147+
}
148+
149+
#[test]
150+
fn unsuccessful_authentication_no_metadata() {
151+
let authenticator = UDSAuthenticator {};
152+
let req_auth = RequestAuth::new("secret".into());
153+
154+
let conn_metadata = None;
155+
let auth_result = authenticator.authenticate(&req_auth, conn_metadata);
156+
assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));
157+
}
158+
159+
#[test]
160+
fn unsuccessful_authentication_wrong_metadata() {
161+
// TODO: this test needs implementing when we have more than one metadata type in
162+
// `PeerCredentials::ConnectionMetadata`. At the moment, the compiler just complains with
163+
// an 'unreachable branch' message.
164+
}
165+
}

src/front/domain_socket.rs

+16-4
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
//! Expose Parsec functionality using Unix domain sockets as an IPC layer.
66
//! The local socket is created at a predefined location.
77
use super::listener;
8-
use listener::Connection;
98
use listener::Listen;
9+
use listener::{Connection, ConnectionMetadata, GetMetadata};
1010
use log::error;
1111
#[cfg(not(feature = "no-parsec-user-and-clients-group"))]
1212
use std::ffi::CString;
@@ -16,6 +16,7 @@ use std::io::{Error, ErrorKind, Result};
1616
use std::os::unix::fs::PermissionsExt;
1717
use std::os::unix::io::FromRawFd;
1818
use std::os::unix::net::UnixListener;
19+
use std::os::unix::net::UnixStream;
1920
use std::path::Path;
2021
use std::time::Duration;
2122

@@ -202,11 +203,10 @@ impl Listen for DomainSocketListener {
202203
format_error!("Failed to set stream as blocking", err);
203204
None
204205
} else {
206+
let metadata = stream.metadata();
205207
Some(Connection {
206208
stream: Box::new(stream),
207-
// TODO: when possible, we want to replace this with the (uid, gid, pid)
208-
// triple for peer credentials. See listener.rs.
209-
metadata: None,
209+
metadata: metadata,
210210
})
211211
}
212212
}
@@ -245,3 +245,15 @@ impl DomainSocketListenerBuilder {
245245
})?)
246246
}
247247
}
248+
249+
impl GetMetadata for UnixStream {
250+
fn metadata(&self) -> Option<ConnectionMetadata> {
251+
let ucred = self
252+
.peer_cred()
253+
.expect("Failed to get peer credentials for Unix domain socket connection.");
254+
Some(ConnectionMetadata::PeerCredentials {
255+
uid: ucred.uid,
256+
gid: ucred.gid,
257+
})
258+
}
259+
}

src/front/listener.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub struct ListenerConfig {
2929
/// Specifies metadata associated with a connection, if any.
3030
#[derive(Copy, Clone, Debug)]
3131
pub enum ConnectionMetadata {
32-
// TODO: nothing here right now. Metadata types will be added as needed.
32+
PeerCredentials { uid: u32, gid: u32 },
3333
}
3434

3535
/// Represents a connection to a single client. Contains a stream, used for communication with the
@@ -65,3 +65,9 @@ pub trait Listen {
6565
/// If the listener has not been initialised before, with the `init` method.
6666
fn accept(&self) -> Option<Connection>;
6767
}
68+
69+
/// Get metadata for a particular object.
70+
pub trait GetMetadata {
71+
/// Get the metadata associated with this object.
72+
fn metadata(&self) -> Option<ConnectionMetadata>;
73+
}

src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
)]
3737
// This one is hard to avoid.
3838
#![allow(clippy::multiple_crate_versions)]
39+
// TODO: remove this if/when the Unix peer credentials PR gets merged. Link
40+
// here for reference: https://github.com/rust-lang/rust/pull/75148
41+
#![feature(peer_credentials_unix_socket)]
3942

4043
#[allow(unused)]
4144
macro_rules! format_error {

0 commit comments

Comments
 (0)