Skip to content

Commit 393aaab

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 393aaab

File tree

6 files changed

+212
-7
lines changed

6 files changed

+212
-7
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+
peer-credentials-authenticator = []

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

src/front/domain_socket.rs

+23-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
}
@@ -248,3 +248,22 @@ impl DomainSocketListenerBuilder {
248248
})?)
249249
}
250250
}
251+
252+
impl GetMetadata for UnixStream {
253+
#[cfg(feature = "peer-credentials-authenticator")]
254+
fn metadata(&self) -> Option<ConnectionMetadata> {
255+
let ucred = self
256+
.peer_cred()
257+
.expect("Failed to get peer credentials for Unix domain socket connection.");
258+
Some(ConnectionMetadata::PeerCredentials {
259+
uid: ucred.uid,
260+
gid: ucred.gid,
261+
})
262+
}
263+
264+
// If peer credentials authenticator feature is not in use, return None for the metadata.
265+
#[cfg(not(feature = "peer-credentials-authenticator"))]
266+
fn metadata(&self) -> Option<ConnectionMetadata> {
267+
None
268+
}
269+
}

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+
/// Peer credentials metadat for Unix domain sockets.
38+
PeerCredentials {
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 = "peer-credentials-authenticator",
42+
feature(peer_credentials_unix_socket)
43+
)]
3844

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

0 commit comments

Comments
 (0)