Skip to content

Commit 08982e9

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 08982e9

File tree

6 files changed

+236
-8
lines changed

6 files changed

+236
-8
lines changed

Cargo.toml

+1
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

+4-3
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
///
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
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_string().as_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_string().as_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+
}

src/front/domain_socket.rs

+31-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,
210210
})
211211
}
212212
}
@@ -248,3 +248,30 @@ 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
256+
.peer_cred()
257+
.map_err(|err| {
258+
format_error!(
259+
"Failed to grab peer credentials metadata from UnixStream",
260+
err
261+
);
262+
err
263+
})
264+
.ok()?;
265+
266+
Some(ConnectionMetadata::UnixPeerCredentials {
267+
uid: ucred.uid,
268+
gid: ucred.gid,
269+
})
270+
}
271+
272+
// If Unix peer credentials authenticator feature is not in use, return None for the metadata.
273+
#[cfg(not(feature = "unix-peer-credentials-authenticator"))]
274+
fn metadata(&self) -> Option<ConnectionMetadata> {
275+
None
276+
}
277+
}

src/front/listener.rs

+13-1
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

+6
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)