Skip to content

Commit 2a57277

Browse files
authored
Merge pull request #214 from joechrisellis/uds_authenticator
Add Unix peer credentials authenticator
2 parents b80416b + 0b48895 commit 2a57277

File tree

5 files changed

+355
-8
lines changed

5 files changed

+355
-8
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ picky-asn1-x509 = { version = "0.3.2", optional = true }
4444
users = "0.10.0"
4545
libc = "0.2.77"
4646

47+
[dev-dependencies]
48+
rand = { version = "0.7.3", features = ["small_rng"] }
49+
4750
[package.metadata.docs.rs]
4851
features = ["pkcs11-provider", "tpm-provider", "tss-esapi/docs", "mbed-crypto-provider"]
4952

src/authenticators/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
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+
pub mod unix_peer_credentials_authenticator;
15+
1616
use crate::front::listener::ConnectionMetadata;
1717
use parsec_interface::operations::list_authenticators;
1818
use parsec_interface::requests::request::RequestAuth;
@@ -35,7 +35,7 @@ pub trait Authenticate {
3535
/// Authenticates a `RequestAuth` payload and returns the `ApplicationName` if successful. A
3636
/// optional `ConnectionMetadata` object is passed in too, since it is sometimes possible to
3737
/// perform authentication based on the connection's metadata (i.e. as is the case for UNIX
38-
/// domain sockets with peer credentials).
38+
/// domain sockets with Unix peer credentials).
3939
///
4040
/// # Errors
4141
///
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
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+
50+
const EXPECTED_UID_SIZE_BYTES: usize = 4;
51+
let expected_uid: [u8; EXPECTED_UID_SIZE_BYTES] =
52+
expected_uid_bytes.as_slice().try_into().map_err(|_| {
53+
error!(
54+
"UID in authentication request is not the right size (expected: {}, got: {}).",
55+
EXPECTED_UID_SIZE_BYTES,
56+
expected_uid_bytes.len()
57+
);
58+
ResponseStatus::AuthenticationError
59+
})?;
60+
let expected_uid = u32::from_le_bytes(expected_uid);
61+
62+
let meta = meta.ok_or_else(|| {
63+
error!("Authenticator did not receive any metadata; cannot perform authentication.");
64+
ResponseStatus::AuthenticationError
65+
})?;
66+
67+
#[allow(unreachable_patterns)]
68+
let (uid, _gid, _pid) = match meta {
69+
ConnectionMetadata::UnixPeerCredentials { uid, gid, pid } => (uid, gid, pid),
70+
_ => {
71+
error!("Wrong metadata type given to Unix peer credentials authenticator.");
72+
return Err(ResponseStatus::AuthenticationError);
73+
}
74+
};
75+
76+
// Authentication is successful if the _actual_ UID from the Unix peer credentials equals
77+
// the self-declared UID in the authentication request.
78+
if uid == expected_uid {
79+
Ok(ApplicationName(uid.to_string()))
80+
} else {
81+
error!("Declared UID in authentication request does not match the process's UID.");
82+
Err(ResponseStatus::AuthenticationError)
83+
}
84+
}
85+
}
86+
87+
#[cfg(test)]
88+
mod test {
89+
use super::super::Authenticate;
90+
use super::UnixPeerCredentialsAuthenticator;
91+
use crate::front::domain_socket::peer_credentials;
92+
use crate::front::listener::ConnectionMetadata;
93+
use parsec_interface::requests::request::RequestAuth;
94+
use parsec_interface::requests::ResponseStatus;
95+
use rand::Rng;
96+
use std::os::unix::net::UnixStream;
97+
use users::get_current_uid;
98+
99+
#[test]
100+
fn successful_authentication() {
101+
// This test should PASS; we are verifying that our username gets set as the application
102+
// secret when using Unix peer credentials authentication with Unix domain sockets.
103+
104+
// Create two connected sockets.
105+
let (sock_a, _sock_b) = UnixStream::pair().unwrap();
106+
let (cred_a, _cred_b) = (
107+
peer_credentials::peer_cred(&sock_a).unwrap(),
108+
peer_credentials::peer_cred(&_sock_b).unwrap(),
109+
);
110+
111+
let authenticator = UnixPeerCredentialsAuthenticator {};
112+
113+
let req_auth_data = cred_a.uid.to_le_bytes().to_vec();
114+
let req_auth = RequestAuth::new(req_auth_data);
115+
let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials {
116+
uid: cred_a.uid,
117+
gid: cred_a.gid,
118+
pid: None,
119+
});
120+
121+
let auth_name = authenticator
122+
.authenticate(&req_auth, conn_metadata)
123+
.expect("Failed to authenticate");
124+
125+
assert_eq!(auth_name.get_name(), get_current_uid().to_string());
126+
}
127+
128+
#[test]
129+
fn unsuccessful_authentication_wrong_declared_uid() {
130+
// This test should FAIL; we are trying to authenticate, but we are declaring the wrong
131+
// UID.
132+
133+
// Create two connected sockets.
134+
let (sock_a, _sock_b) = UnixStream::pair().unwrap();
135+
let (cred_a, _cred_b) = (
136+
peer_credentials::peer_cred(&sock_a).unwrap(),
137+
peer_credentials::peer_cred(&_sock_b).unwrap(),
138+
);
139+
140+
let authenticator = UnixPeerCredentialsAuthenticator {};
141+
142+
let wrong_uid = cred_a.uid + 1;
143+
let wrong_req_auth_data = wrong_uid.to_le_bytes().to_vec();
144+
let req_auth = RequestAuth::new(wrong_req_auth_data);
145+
let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials {
146+
uid: cred_a.uid,
147+
gid: cred_a.gid,
148+
pid: cred_a.pid,
149+
});
150+
151+
let auth_result = authenticator.authenticate(&req_auth, conn_metadata);
152+
assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));
153+
}
154+
155+
#[test]
156+
fn unsuccessful_authentication_garbage_data() {
157+
// This test should FAIL; we are sending garbage (random) data in the request.
158+
159+
// Create two connected sockets.
160+
let (sock_a, _sock_b) = UnixStream::pair().unwrap();
161+
let (cred_a, _cred_b) = (
162+
peer_credentials::peer_cred(&sock_a).unwrap(),
163+
peer_credentials::peer_cred(&_sock_b).unwrap(),
164+
);
165+
166+
let authenticator = UnixPeerCredentialsAuthenticator {};
167+
168+
let garbage_data = rand::thread_rng().gen::<[u8; 32]>().to_vec();
169+
let req_auth = RequestAuth::new(garbage_data);
170+
let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials {
171+
uid: cred_a.uid,
172+
gid: cred_a.gid,
173+
pid: cred_a.pid,
174+
});
175+
176+
let auth_result = authenticator.authenticate(&req_auth, conn_metadata);
177+
assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));
178+
}
179+
180+
#[test]
181+
fn unsuccessful_authentication_no_metadata() {
182+
let authenticator = UnixPeerCredentialsAuthenticator {};
183+
let req_auth = RequestAuth::new("secret".into());
184+
185+
let conn_metadata = None;
186+
let auth_result = authenticator.authenticate(&req_auth, conn_metadata);
187+
assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));
188+
}
189+
190+
#[test]
191+
fn unsuccessful_authentication_wrong_metadata() {
192+
// TODO(new_metadata_variant): this test needs implementing when we have more than one
193+
// metadata type. At the moment, the compiler just complains with an 'unreachable branch'
194+
// message.
195+
}
196+
}

src/front/domain_socket.rs

Lines changed: 140 additions & 4 deletions
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};
1010
use log::error;
1111
#[cfg(not(feature = "no-parsec-user-and-clients-group"))]
1212
use std::ffi::CString;
@@ -202,11 +202,22 @@ impl Listen for DomainSocketListener {
202202
format_error!("Failed to set stream as blocking", err);
203203
None
204204
} else {
205+
let ucred = peer_credentials::peer_cred(&stream)
206+
.map_err(|err| {
207+
format_error!(
208+
"Failed to grab peer credentials metadata from UnixStream",
209+
err
210+
);
211+
err
212+
})
213+
.ok()?;
205214
Some(Connection {
206215
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,
216+
metadata: Some(ConnectionMetadata::UnixPeerCredentials {
217+
uid: ucred.uid,
218+
gid: ucred.gid,
219+
pid: ucred.pid,
220+
}),
210221
})
211222
}
212223
}
@@ -248,3 +259,128 @@ impl DomainSocketListenerBuilder {
248259
})?)
249260
}
250261
}
262+
263+
// == IMPORTANT NOTE ==
264+
//
265+
// The code below has been cherry-picked from the following PR:
266+
//
267+
// https://github.com/rust-lang/rust/pull/75148
268+
//
269+
// At the time of writing (16/09/20), this patch is in the nightly Rust channel. To avoid needing
270+
// to use the nightly compiler to build Parsec, we have instead opted to cherry-pick the change
271+
// from the patch to allow us to use this feature 'early'.
272+
//
273+
// Once the feature hits stable, it should be safe to revert the commit that introduced the changes
274+
// below with `git revert`. You can find the stabilizing Rust issue here:
275+
//
276+
// https://github.com/rust-lang/rust/issues/42839
277+
278+
/// Implementation of peer credentials fetching for Unix domain socket.
279+
pub mod peer_credentials {
280+
use libc::{gid_t, pid_t, uid_t};
281+
282+
/// Credentials for a UNIX process for credentials passing.
283+
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
284+
pub struct UCred {
285+
/// The UID part of the peer credential. This is the effective UID of the process at the domain
286+
/// socket's endpoint.
287+
pub uid: uid_t,
288+
/// The GID part of the peer credential. This is the effective GID of the process at the domain
289+
/// socket's endpoint.
290+
pub gid: gid_t,
291+
/// The PID part of the peer credential. This field is optional because the PID part of the
292+
/// peer credentials is not supported on every platform. On platforms where the mechanism to
293+
/// discover the PID exists, this field will be populated to the PID of the process at the
294+
/// domain socket's endpoint. Otherwise, it will be set to None.
295+
pub pid: Option<pid_t>,
296+
}
297+
298+
#[cfg(any(target_os = "android", target_os = "linux"))]
299+
pub use self::impl_linux::peer_cred;
300+
301+
#[cfg(any(
302+
target_os = "dragonfly",
303+
target_os = "freebsd",
304+
target_os = "ios",
305+
target_os = "macos",
306+
target_os = "openbsd"
307+
))]
308+
pub use self::impl_bsd::peer_cred;
309+
310+
#[cfg(any(target_os = "linux", target_os = "android"))]
311+
#[allow(missing_docs, trivial_casts)] // docs not required; only used for selective compilation.
312+
pub mod impl_linux {
313+
use super::UCred;
314+
use libc::{c_void, getsockopt, socklen_t, ucred, SOL_SOCKET, SO_PEERCRED};
315+
use std::os::unix::io::AsRawFd;
316+
use std::os::unix::net::UnixStream;
317+
use std::{io, mem};
318+
319+
pub fn peer_cred(socket: &UnixStream) -> io::Result<UCred> {
320+
let ucred_size = mem::size_of::<ucred>();
321+
322+
// Trivial sanity checks.
323+
assert!(mem::size_of::<u32>() <= mem::size_of::<usize>());
324+
assert!(ucred_size <= u32::MAX as usize);
325+
326+
let mut ucred_size = ucred_size as socklen_t;
327+
let mut ucred: ucred = ucred {
328+
pid: 1,
329+
uid: 1,
330+
gid: 1,
331+
};
332+
333+
unsafe {
334+
let ret = getsockopt(
335+
socket.as_raw_fd(),
336+
SOL_SOCKET,
337+
SO_PEERCRED,
338+
&mut ucred as *mut ucred as *mut c_void,
339+
&mut ucred_size,
340+
);
341+
342+
if ret == 0 && ucred_size as usize == mem::size_of::<ucred>() {
343+
Ok(UCred {
344+
uid: ucred.uid,
345+
gid: ucred.gid,
346+
pid: Some(ucred.pid),
347+
})
348+
} else {
349+
Err(io::Error::last_os_error())
350+
}
351+
}
352+
}
353+
}
354+
355+
#[cfg(any(
356+
target_os = "dragonfly",
357+
target_os = "macos",
358+
target_os = "ios",
359+
target_os = "freebsd",
360+
target_os = "openbsd"
361+
))]
362+
#[allow(missing_docs)] // docs not required; only used for selective compilation.
363+
pub mod impl_bsd {
364+
use super::UCred;
365+
use std::io;
366+
use std::os::unix::io::AsRawFd;
367+
use std::os::unix::net::UnixStream;
368+
369+
pub fn peer_cred(socket: &UnixStream) -> io::Result<UCred> {
370+
let mut cred = UCred {
371+
uid: 1,
372+
gid: 1,
373+
pid: None,
374+
};
375+
unsafe {
376+
let ret = libc::getpeereid(socket.as_raw_fd(), &mut cred.uid, &mut cred.gid);
377+
378+
if ret == 0 {
379+
Ok(cred)
380+
} else {
381+
Err(io::Error::last_os_error())
382+
}
383+
}
384+
}
385+
}
386+
}

0 commit comments

Comments
 (0)