diff --git a/Cargo.toml b/Cargo.toml
index c6844f9d..88955daf 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -44,6 +44,9 @@ picky-asn1-x509 = { version = "0.3.2", optional = true }
 users = "0.10.0"
 libc = "0.2.77"
 
+[dev-dependencies]
+rand = { version = "0.7.3", features = ["small_rng"] }
+
 [package.metadata.docs.rs]
 features = ["pkcs11-provider", "tpm-provider", "tss-esapi/docs", "mbed-crypto-provider"]
 
diff --git a/src/authenticators/mod.rs b/src/authenticators/mod.rs
index 612e33bf..5ac29025 100644
--- a/src/authenticators/mod.rs
+++ b/src/authenticators/mod.rs
@@ -8,11 +8,11 @@
 //! used throughout the service for identifying the request initiator. The input to an authentication
 //! is the `RequestAuth` field of a request, which is parsed by the authenticator specified in the header.
 //! The authentication functionality is abstracted through an `Authenticate` trait.
-//!
-//! Currently only a simple Direct Authenticator component is implemented.
 
 pub mod direct_authenticator;
 
+pub mod unix_peer_credentials_authenticator;
+
 use crate::front::listener::ConnectionMetadata;
 use parsec_interface::operations::list_authenticators;
 use parsec_interface::requests::request::RequestAuth;
@@ -35,7 +35,7 @@ pub trait Authenticate {
     /// Authenticates a `RequestAuth` payload and returns the `ApplicationName` if successful. A
     /// optional `ConnectionMetadata` object is passed in too, since it is sometimes possible to
     /// perform authentication based on the connection's metadata (i.e. as is the case for UNIX
-    /// domain sockets with peer credentials).
+    /// domain sockets with Unix peer credentials).
     ///
     /// # Errors
     ///
diff --git a/src/authenticators/unix_peer_credentials_authenticator/mod.rs b/src/authenticators/unix_peer_credentials_authenticator/mod.rs
new file mode 100644
index 00000000..1e4ae94d
--- /dev/null
+++ b/src/authenticators/unix_peer_credentials_authenticator/mod.rs
@@ -0,0 +1,196 @@
+// Copyright 2020 Contributors to the Parsec project.
+// SPDX-License-Identifier: Apache-2.0
+//! Unix peer credentials authenticator
+//!
+//! The `UnixPeerCredentialsAuthenticator` uses Unix peer credentials to perform authentication. As
+//! such, it uses the effective Unix user ID (UID) to authenticate the connecting process. Unix
+//! peer credentials also allow us to access the effective Unix group ID (GID) of the connecting
+//! process, although this information is currently unused.
+//!
+//! Currently, the stringified UID is used as the application name.
+
+use super::ApplicationName;
+use super::Authenticate;
+use crate::front::listener::ConnectionMetadata;
+use log::error;
+use parsec_interface::operations::list_authenticators;
+use parsec_interface::requests::request::RequestAuth;
+use parsec_interface::requests::AuthType;
+use parsec_interface::requests::{ResponseStatus, Result};
+use parsec_interface::secrecy::ExposeSecret;
+use std::convert::TryInto;
+
+/// Unix peer credentials authenticator.
+#[derive(Copy, Clone, Debug)]
+pub struct UnixPeerCredentialsAuthenticator;
+
+impl Authenticate for UnixPeerCredentialsAuthenticator {
+    fn describe(&self) -> Result<list_authenticators::AuthenticatorInfo> {
+        Ok(list_authenticators::AuthenticatorInfo {
+            description: String::from(
+                "Uses Unix peer credentials to authenticate the client. Verifies that the self-declared \
+                Unix user identifier (UID) in the request's authentication header matches that which is \
+                found from the peer credentials."
+            ),
+            version_maj: 0,
+            version_min: 1,
+            version_rev: 0,
+            id: AuthType::PeerCredentials,
+        })
+    }
+
+    fn authenticate(
+        &self,
+        auth: &RequestAuth,
+        meta: Option<ConnectionMetadata>,
+    ) -> Result<ApplicationName> {
+        // Parse authentication request.
+        let expected_uid_bytes = auth.buffer.expose_secret();
+
+        const EXPECTED_UID_SIZE_BYTES: usize = 4;
+        let expected_uid: [u8; EXPECTED_UID_SIZE_BYTES] =
+            expected_uid_bytes.as_slice().try_into().map_err(|_| {
+                error!(
+                    "UID in authentication request is not the right size (expected: {}, got: {}).",
+                    EXPECTED_UID_SIZE_BYTES,
+                    expected_uid_bytes.len()
+                );
+                ResponseStatus::AuthenticationError
+            })?;
+        let expected_uid = u32::from_le_bytes(expected_uid);
+
+        let meta = meta.ok_or_else(|| {
+            error!("Authenticator did not receive any metadata; cannot perform authentication.");
+            ResponseStatus::AuthenticationError
+        })?;
+
+        #[allow(unreachable_patterns)]
+        let (uid, _gid, _pid) = match meta {
+            ConnectionMetadata::UnixPeerCredentials { uid, gid, pid } => (uid, gid, pid),
+            _ => {
+                error!("Wrong metadata type given to Unix peer credentials authenticator.");
+                return Err(ResponseStatus::AuthenticationError);
+            }
+        };
+
+        // Authentication is successful if the _actual_ UID from the Unix peer credentials equals
+        // the self-declared UID in the authentication request.
+        if uid == expected_uid {
+            Ok(ApplicationName(uid.to_string()))
+        } else {
+            error!("Declared UID in authentication request does not match the process's UID.");
+            Err(ResponseStatus::AuthenticationError)
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::super::Authenticate;
+    use super::UnixPeerCredentialsAuthenticator;
+    use crate::front::domain_socket::peer_credentials;
+    use crate::front::listener::ConnectionMetadata;
+    use parsec_interface::requests::request::RequestAuth;
+    use parsec_interface::requests::ResponseStatus;
+    use rand::Rng;
+    use std::os::unix::net::UnixStream;
+    use users::get_current_uid;
+
+    #[test]
+    fn successful_authentication() {
+        // This test should PASS; we are verifying that our username gets set as the application
+        // secret when using Unix peer credentials authentication with Unix domain sockets.
+
+        // Create two connected sockets.
+        let (sock_a, _sock_b) = UnixStream::pair().unwrap();
+        let (cred_a, _cred_b) = (
+            peer_credentials::peer_cred(&sock_a).unwrap(),
+            peer_credentials::peer_cred(&_sock_b).unwrap(),
+        );
+
+        let authenticator = UnixPeerCredentialsAuthenticator {};
+
+        let req_auth_data = cred_a.uid.to_le_bytes().to_vec();
+        let req_auth = RequestAuth::new(req_auth_data);
+        let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials {
+            uid: cred_a.uid,
+            gid: cred_a.gid,
+            pid: None,
+        });
+
+        let auth_name = authenticator
+            .authenticate(&req_auth, conn_metadata)
+            .expect("Failed to authenticate");
+
+        assert_eq!(auth_name.get_name(), get_current_uid().to_string());
+    }
+
+    #[test]
+    fn unsuccessful_authentication_wrong_declared_uid() {
+        // This test should FAIL; we are trying to authenticate, but we are declaring the wrong
+        // UID.
+
+        // Create two connected sockets.
+        let (sock_a, _sock_b) = UnixStream::pair().unwrap();
+        let (cred_a, _cred_b) = (
+            peer_credentials::peer_cred(&sock_a).unwrap(),
+            peer_credentials::peer_cred(&_sock_b).unwrap(),
+        );
+
+        let authenticator = UnixPeerCredentialsAuthenticator {};
+
+        let wrong_uid = cred_a.uid + 1;
+        let wrong_req_auth_data = wrong_uid.to_le_bytes().to_vec();
+        let req_auth = RequestAuth::new(wrong_req_auth_data);
+        let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials {
+            uid: cred_a.uid,
+            gid: cred_a.gid,
+            pid: cred_a.pid,
+        });
+
+        let auth_result = authenticator.authenticate(&req_auth, conn_metadata);
+        assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));
+    }
+
+    #[test]
+    fn unsuccessful_authentication_garbage_data() {
+        // This test should FAIL; we are sending garbage (random) data in the request.
+
+        // Create two connected sockets.
+        let (sock_a, _sock_b) = UnixStream::pair().unwrap();
+        let (cred_a, _cred_b) = (
+            peer_credentials::peer_cred(&sock_a).unwrap(),
+            peer_credentials::peer_cred(&_sock_b).unwrap(),
+        );
+
+        let authenticator = UnixPeerCredentialsAuthenticator {};
+
+        let garbage_data = rand::thread_rng().gen::<[u8; 32]>().to_vec();
+        let req_auth = RequestAuth::new(garbage_data);
+        let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials {
+            uid: cred_a.uid,
+            gid: cred_a.gid,
+            pid: cred_a.pid,
+        });
+
+        let auth_result = authenticator.authenticate(&req_auth, conn_metadata);
+        assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));
+    }
+
+    #[test]
+    fn unsuccessful_authentication_no_metadata() {
+        let authenticator = UnixPeerCredentialsAuthenticator {};
+        let req_auth = RequestAuth::new("secret".into());
+
+        let conn_metadata = None;
+        let auth_result = authenticator.authenticate(&req_auth, conn_metadata);
+        assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));
+    }
+
+    #[test]
+    fn unsuccessful_authentication_wrong_metadata() {
+        // TODO(new_metadata_variant): this test needs implementing when we have more than one
+        // metadata type. At the moment, the compiler just complains with an 'unreachable branch'
+        // message.
+    }
+}
diff --git a/src/front/domain_socket.rs b/src/front/domain_socket.rs
index b0e0879d..399801b0 100644
--- a/src/front/domain_socket.rs
+++ b/src/front/domain_socket.rs
@@ -5,8 +5,8 @@
 //! Expose Parsec functionality using Unix domain sockets as an IPC layer.
 //! The local socket is created at a predefined location.
 use super::listener;
-use listener::Connection;
 use listener::Listen;
+use listener::{Connection, ConnectionMetadata};
 use log::error;
 #[cfg(not(feature = "no-parsec-user-and-clients-group"))]
 use std::ffi::CString;
@@ -202,11 +202,22 @@ impl Listen for DomainSocketListener {
                     format_error!("Failed to set stream as blocking", err);
                     None
                 } else {
+                    let ucred = peer_credentials::peer_cred(&stream)
+                        .map_err(|err| {
+                            format_error!(
+                                "Failed to grab peer credentials metadata from UnixStream",
+                                err
+                            );
+                            err
+                        })
+                        .ok()?;
                     Some(Connection {
                         stream: Box::new(stream),
-                        // TODO: when possible, we want to replace this with the (uid, gid, pid)
-                        // triple for peer credentials. See listener.rs.
-                        metadata: None,
+                        metadata: Some(ConnectionMetadata::UnixPeerCredentials {
+                            uid: ucred.uid,
+                            gid: ucred.gid,
+                            pid: ucred.pid,
+                        }),
                     })
                 }
             }
@@ -248,3 +259,128 @@ impl DomainSocketListenerBuilder {
         })?)
     }
 }
+
+// == IMPORTANT NOTE ==
+//
+// The code below has been cherry-picked from the following PR:
+//
+//     https://github.com/rust-lang/rust/pull/75148
+//
+// At the time of writing (16/09/20), this patch is in the nightly Rust channel. To avoid needing
+// to use the nightly compiler to build Parsec, we have instead opted to cherry-pick the change
+// from the patch to allow us to use this feature 'early'.
+//
+// Once the feature hits stable, it should be safe to revert the commit that introduced the changes
+// below with `git revert`. You can find the stabilizing Rust issue here:
+//
+//     https://github.com/rust-lang/rust/issues/42839
+
+/// Implementation of peer credentials fetching for Unix domain socket.
+pub mod peer_credentials {
+    use libc::{gid_t, pid_t, uid_t};
+
+    /// Credentials for a UNIX process for credentials passing.
+    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+    pub struct UCred {
+        /// The UID part of the peer credential. This is the effective UID of the process at the domain
+        /// socket's endpoint.
+        pub uid: uid_t,
+        /// The GID part of the peer credential. This is the effective GID of the process at the domain
+        /// socket's endpoint.
+        pub gid: gid_t,
+        /// The PID part of the peer credential. This field is optional because the PID part of the
+        /// peer credentials is not supported on every platform. On platforms where the mechanism to
+        /// discover the PID exists, this field will be populated to the PID of the process at the
+        /// domain socket's endpoint. Otherwise, it will be set to None.
+        pub pid: Option<pid_t>,
+    }
+
+    #[cfg(any(target_os = "android", target_os = "linux"))]
+    pub use self::impl_linux::peer_cred;
+
+    #[cfg(any(
+        target_os = "dragonfly",
+        target_os = "freebsd",
+        target_os = "ios",
+        target_os = "macos",
+        target_os = "openbsd"
+    ))]
+    pub use self::impl_bsd::peer_cred;
+
+    #[cfg(any(target_os = "linux", target_os = "android"))]
+    #[allow(missing_docs, trivial_casts)] // docs not required; only used for selective compilation.
+    pub mod impl_linux {
+        use super::UCred;
+        use libc::{c_void, getsockopt, socklen_t, ucred, SOL_SOCKET, SO_PEERCRED};
+        use std::os::unix::io::AsRawFd;
+        use std::os::unix::net::UnixStream;
+        use std::{io, mem};
+
+        pub fn peer_cred(socket: &UnixStream) -> io::Result<UCred> {
+            let ucred_size = mem::size_of::<ucred>();
+
+            // Trivial sanity checks.
+            assert!(mem::size_of::<u32>() <= mem::size_of::<usize>());
+            assert!(ucred_size <= u32::MAX as usize);
+
+            let mut ucred_size = ucred_size as socklen_t;
+            let mut ucred: ucred = ucred {
+                pid: 1,
+                uid: 1,
+                gid: 1,
+            };
+
+            unsafe {
+                let ret = getsockopt(
+                    socket.as_raw_fd(),
+                    SOL_SOCKET,
+                    SO_PEERCRED,
+                    &mut ucred as *mut ucred as *mut c_void,
+                    &mut ucred_size,
+                );
+
+                if ret == 0 && ucred_size as usize == mem::size_of::<ucred>() {
+                    Ok(UCred {
+                        uid: ucred.uid,
+                        gid: ucred.gid,
+                        pid: Some(ucred.pid),
+                    })
+                } else {
+                    Err(io::Error::last_os_error())
+                }
+            }
+        }
+    }
+
+    #[cfg(any(
+        target_os = "dragonfly",
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "freebsd",
+        target_os = "openbsd"
+    ))]
+    #[allow(missing_docs)] // docs not required; only used for selective compilation.
+    pub mod impl_bsd {
+        use super::UCred;
+        use std::io;
+        use std::os::unix::io::AsRawFd;
+        use std::os::unix::net::UnixStream;
+
+        pub fn peer_cred(socket: &UnixStream) -> io::Result<UCred> {
+            let mut cred = UCred {
+                uid: 1,
+                gid: 1,
+                pid: None,
+            };
+            unsafe {
+                let ret = libc::getpeereid(socket.as_raw_fd(), &mut cred.uid, &mut cred.gid);
+
+                if ret == 0 {
+                    Ok(cred)
+                } else {
+                    Err(io::Error::last_os_error())
+                }
+            }
+        }
+    }
+}
diff --git a/src/front/listener.rs b/src/front/listener.rs
index 914de6f4..cd8b7e04 100644
--- a/src/front/listener.rs
+++ b/src/front/listener.rs
@@ -34,7 +34,19 @@ pub struct ListenerConfig {
 /// Specifies metadata associated with a connection, if any.
 #[derive(Copy, Clone, Debug)]
 pub enum ConnectionMetadata {
-    // TODO: nothing here right now. Metadata types will be added as needed.
+    /// Unix peer credentials metadata for Unix domain sockets.
+    UnixPeerCredentials {
+        /// The effective UID of the connecting process.
+        uid: u32,
+        /// The effective GID of the connecting process.
+        gid: u32,
+        /// The optional PID of the connecting process. This is an Option<u32> because not all
+        /// platforms support retrieving PID via a domain socket.
+        pid: Option<i32>,
+    },
+    // NOTE: there is currently only _one_ variant of the ConnectionMetadata enum. When a second
+    //       variant is added, you will need to update some tests!
+    //       You should grep the tests for `TODO(new_metadata_variant)` and update them accordingly.
 }
 
 /// Represents a connection to a single client