Skip to content

Commit 822feb4

Browse files
author
Joe Ellis
committed
Add peer credentials authenticator
This authenticator uses peer credentials for authentication. The specific type of peer credentials in mind at the moment are Unix peer credentials, but this can be extended in the future. 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 trivially determine the uid/gid of the connecting process, which we can then use for authentication. 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 `peer-credentials-authenticator`. Note that gid is currently unused by the authenticator. Also 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 54cc197 commit 822feb4

File tree

6 files changed

+241
-8
lines changed

6 files changed

+241
-8
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,4 @@ 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"]
7272
docs = ["pkcs11-provider", "tpm-provider", "tss-esapi/docs", "mbed-crypto-provider"]
73+
unix-peer-credentials-authenticator = []

src/authenticators/mod.rs

Lines changed: 4 additions & 3 deletions
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 = "unix-peer-credentials-authenticator")]
15+
pub mod unix_peer_credentials_authenticator;
16+
1617
use crate::front::listener::ConnectionMetadata;
1718
use parsec_interface::operations::list_authenticators;
1819
use parsec_interface::requests::request::RequestAuth;
@@ -35,7 +36,7 @@ pub trait Authenticate {
3536
/// Authenticates a `RequestAuth` payload and returns the `ApplicationName` if successful. A
3637
/// optional `ConnectionMetadata` object is passed in too, since it is sometimes possible to
3738
/// perform authentication based on the connection's metadata (i.e. as is the case for UNIX
38-
/// domain sockets with peer credentials).
39+
/// domain sockets with Unix peer credentials).
3940
///
4041
/// # Errors
4142
///
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
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+
#[derive(Copy, Clone, Debug)]
24+
pub struct UnixPeerCredentialsAuthenticator;
25+
26+
impl Authenticate for UnixPeerCredentialsAuthenticator {
27+
fn describe(&self) -> Result<list_authenticators::AuthenticatorInfo> {
28+
Ok(list_authenticators::AuthenticatorInfo {
29+
description: String::from(
30+
"Uses Unix peer credentials to authenticate the client. Verifies that the self-declared \
31+
Unix user identifier (UID) in the request's authentication header matches that which is \
32+
found from the peer credentials."
33+
),
34+
version_maj: 0,
35+
version_min: 1,
36+
version_rev: 0,
37+
id: AuthType::PeerCredentials,
38+
})
39+
}
40+
41+
fn authenticate(
42+
&self,
43+
auth: &RequestAuth,
44+
meta: Option<ConnectionMetadata>,
45+
) -> Result<ApplicationName> {
46+
// Parse authentication request.
47+
let expected_uid_bytes = auth.buffer.expose_secret();
48+
if expected_uid_bytes.is_empty() {
49+
error!("Expected UID in authentication request, but it is empty.");
50+
return Err(ResponseStatus::AuthenticationError);
51+
}
52+
53+
const EXPECTED_UID_SIZE_BYTES: usize = 4;
54+
if expected_uid_bytes.len() != EXPECTED_UID_SIZE_BYTES {
55+
error!(
56+
"UID in authentication request is not the right size (expected: {}, got: {}).",
57+
EXPECTED_UID_SIZE_BYTES,
58+
expected_uid_bytes.len()
59+
);
60+
return Err(ResponseStatus::AuthenticationError);
61+
}
62+
63+
let expected_uid = auth.buffer.expose_secret();
64+
let expected_uid: [u8; 4] = expected_uid_bytes.as_slice().try_into().map_err(|e| {
65+
error!(
66+
"UID in authentication request is not the right size (expected: {}, got: {}).",
67+
EXPECTED_UID_SIZE_BYTES,
68+
expected_uid_bytes.len()
69+
);
70+
ResponseStatus::AuthenticationError
71+
})?;
72+
let expected_uid = u32::from_le_bytes(expected_uid);
73+
74+
let meta = meta.ok_or_else(|| {
75+
error!("Authenticator did not receive any metadata; cannot perform authentication.");
76+
ResponseStatus::AuthenticationError
77+
})?;
78+
79+
let (uid, _gid) = match meta {
80+
ConnectionMetadata::UnixPeerCredentials { uid, gid } => (uid, gid),
81+
// TODO: add wildcard pattern when `ConnectionMetadata` has more possibilities.
82+
};
83+
84+
// Authentication is successful if the _actual_ UID from the Unix peer credentials equals
85+
// the self-declared UID in the authentication request.
86+
if uid == expected_uid {
87+
Ok(ApplicationName(uid.to_string()))
88+
} else {
89+
error!("Declared UID in authentication request does not match the process's UID.");
90+
Err(ResponseStatus::AuthenticationError)
91+
}
92+
}
93+
}
94+
95+
#[cfg(test)]
96+
mod test {
97+
use super::super::Authenticate;
98+
use super::UnixPeerCredentialsAuthenticator;
99+
use crate::front::listener::ConnectionMetadata;
100+
use parsec_interface::requests::request::RequestAuth;
101+
use parsec_interface::requests::ResponseStatus;
102+
use rand::Rng;
103+
use std::os::unix::net::UnixStream;
104+
use users::get_current_uid;
105+
106+
#[test]
107+
fn successful_authentication() {
108+
// This test should PASS; we are verifying that our username gets set as the application
109+
// secret when using Unix peer credentials authentication with Unix domain sockets.
110+
111+
// Create two connected sockets.
112+
let (sock_a, _sock_b) = UnixStream::pair().unwrap();
113+
let (cred_a, _cred_b) = (sock_a.peer_cred().unwrap(), _sock_b.peer_cred().unwrap());
114+
115+
let authenticator = UnixPeerCredentialsAuthenticator {};
116+
117+
let req_auth_data = cred_a.uid.to_string().as_bytes().to_vec();
118+
let req_auth = RequestAuth::new(req_auth_data);
119+
let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials {
120+
uid: cred_a.uid,
121+
gid: cred_a.gid,
122+
});
123+
124+
let auth_name = authenticator
125+
.authenticate(&req_auth, conn_metadata)
126+
.expect("Failed to authenticate");
127+
128+
assert_eq!(auth_name.get_name(), get_current_uid().to_string());
129+
}
130+
131+
#[test]
132+
fn unsuccessful_authentication_wrong_declared_uid() {
133+
// This test should FAIL; we are trying to authenticate, but we are declaring the wrong
134+
// UID.
135+
136+
// Create two connected sockets.
137+
let (sock_a, _sock_b) = UnixStream::pair().unwrap();
138+
let (cred_a, _cred_b) = (sock_a.peer_cred().unwrap(), _sock_b.peer_cred().unwrap());
139+
140+
let authenticator = UnixPeerCredentialsAuthenticator {};
141+
142+
let wrong_uid = cred_a.uid + 1;
143+
let wrong_req_auth_data = wrong_uid.to_string().as_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+
});
149+
150+
let auth_result = authenticator.authenticate(&req_auth, conn_metadata);
151+
assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));
152+
}
153+
154+
#[test]
155+
fn unsuccessful_authentication_garbage_data() {
156+
// This test should FAIL; we are sending garbage (random) data in the request.
157+
158+
// Create two connected sockets.
159+
let (sock_a, _sock_b) = UnixStream::pair().unwrap();
160+
let (cred_a, _cred_b) = (sock_a.peer_cred().unwrap(), _sock_b.peer_cred().unwrap());
161+
162+
let authenticator = UnixPeerCredentialsAuthenticator {};
163+
164+
let garbage_data = rand::thread_rng().gen::<[u8; 32]>().to_vec();
165+
let req_auth = RequestAuth::new(garbage_data);
166+
let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials {
167+
uid: cred_a.uid,
168+
gid: cred_a.gid,
169+
});
170+
171+
let auth_result = authenticator.authenticate(&req_auth, conn_metadata);
172+
assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));
173+
}
174+
175+
#[test]
176+
fn unsuccessful_authentication_no_metadata() {
177+
let authenticator = UnixPeerCredentialsAuthenticator {};
178+
let req_auth = RequestAuth::new("secret".into());
179+
180+
let conn_metadata = None;
181+
let auth_result = authenticator.authenticate(&req_auth, conn_metadata);
182+
assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));
183+
}
184+
185+
#[test]
186+
fn unsuccessful_authentication_wrong_metadata() {
187+
// TODO: this test needs implementing when we have more than one metadata type. At the
188+
// moment, the compiler just complains with an 'unreachable branch' message.
189+
}
190+
}

src/front/domain_socket.rs

Lines changed: 27 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, 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,
210210
})
211211
}
212212
}
@@ -248,3 +248,26 @@ impl DomainSocketListenerBuilder {
248248
})?)
249249
}
250250
}
251+
252+
impl GetMetadata for UnixStream {
253+
#[cfg(feature = "unix-peer-credentials-authenticator")]
254+
fn metadata(&self) -> Option<ConnectionMetadata> {
255+
let ucred = self.peer_cred().or_else(|err| {
256+
format_error!(
257+
"Failed to grab peer credentials metadata from UnixStream",
258+
err
259+
);
260+
None
261+
})?;
262+
Some(ConnectionMetadata::UnixPeerCredentials {
263+
uid: ucred.uid,
264+
gid: ucred.gid,
265+
})
266+
}
267+
268+
// If Unix peer credentials authenticator feature is not in use, return None for the metadata.
269+
#[cfg(not(feature = "unix-peer-credentials-authenticator"))]
270+
fn metadata(&self) -> Option<ConnectionMetadata> {
271+
None
272+
}
273+
}

src/front/listener.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,13 @@ pub struct ListenerConfig {
3434
/// Specifies metadata associated with a connection, if any.
3535
#[derive(Copy, Clone, Debug)]
3636
pub enum ConnectionMetadata {
37-
// TODO: nothing here right now. Metadata types will be added as needed.
37+
/// Unix peer credentials metadata for Unix domain sockets.
38+
UnixPeerCredentials {
39+
/// The effective UID of the connecting process.
40+
uid: u32,
41+
/// The effective GID of the connecting process.
42+
gid: u32,
43+
},
3844
}
3945

4046
/// Represents a connection to a single client
@@ -70,3 +76,9 @@ pub trait Listen {
7076
/// If the listener has not been initialised before, with the `init` method.
7177
fn accept(&self) -> Option<Connection>;
7278
}
79+
80+
/// Get metadata for a particular object.
81+
pub trait GetMetadata {
82+
/// Get the metadata associated with this object.
83+
fn metadata(&self) -> Option<ConnectionMetadata>;
84+
}

src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@
3535
)]
3636
// This one is hard to avoid.
3737
#![allow(clippy::multiple_crate_versions)]
38+
// TODO: remove this if/when the Unix peer credentials PR gets merged. Link
39+
// here for reference: https://github.com/rust-lang/rust/pull/75148
40+
#![cfg_attr(
41+
feature = "unix-peer-credentials-authenticator",
42+
feature(peer_credentials_unix_socket)
43+
)]
3844

3945
#[allow(unused)]
4046
macro_rules! format_error {

0 commit comments

Comments
 (0)