summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-10-05 23:35:37 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-10-05 23:35:37 +0000
commit727d5df64ddee3f93443ba1e9c57a993a60d714f (patch)
tree6c9bad788bb991c5f40d5e516f50878e0c339941
parentb7560bcad944e3a043183e27ce63ec7457d623c2 (diff)
parenta71c519140e8ca3c0dbfd502947a66eccb410527 (diff)
downloadsecurity-android13-qpr2-s6-release.tar.gz
Change-Id: I79bd39487d24283d3b62228d47f169b6753f575e
-rw-r--r--prng_seeder/Android.bp49
-rw-r--r--prng_seeder/cutils_wrapper.h15
-rw-r--r--prng_seeder/prng_seeder.rc12
-rw-r--r--prng_seeder/src/conditioner.rs76
-rw-r--r--prng_seeder/src/cutils_socket.rs25
-rw-r--r--prng_seeder/src/drbg.rs65
-rw-r--r--prng_seeder/src/main.rs136
7 files changed, 378 insertions, 0 deletions
diff --git a/prng_seeder/Android.bp b/prng_seeder/Android.bp
new file mode 100644
index 00000000..f99dc929
--- /dev/null
+++ b/prng_seeder/Android.bp
@@ -0,0 +1,49 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
+rust_bindgen {
+ name: "libcutils_socket_bindgen",
+ crate_name: "cutils_socket_bindgen",
+ wrapper_src: "cutils_wrapper.h",
+ source_stem: "bindings",
+ bindgen_flags: [
+ "--allowlist-function=android_get_control_socket",
+ ],
+ shared_libs: [
+ "libcutils",
+ ],
+}
+
+rust_binary {
+ name: "prng_seeder",
+ edition: "2021",
+ srcs: ["src/main.rs"],
+ rustlibs: [
+ "libanyhow",
+ "libbssl_ffi",
+ "libcutils_socket_bindgen",
+ "liblogger",
+ "liblog_rust",
+ "libnix",
+ "libtokio",
+ ],
+
+ init_rc: ["prng_seeder.rc"],
+}
diff --git a/prng_seeder/cutils_wrapper.h b/prng_seeder/cutils_wrapper.h
new file mode 100644
index 00000000..9c1fe565
--- /dev/null
+++ b/prng_seeder/cutils_wrapper.h
@@ -0,0 +1,15 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <cutils/sockets.h>
diff --git a/prng_seeder/prng_seeder.rc b/prng_seeder/prng_seeder.rc
new file mode 100644
index 00000000..9825583a
--- /dev/null
+++ b/prng_seeder/prng_seeder.rc
@@ -0,0 +1,12 @@
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Start PRNG seeder daemon from early-init
+
+on early-init
+ start prng_seeder
+
+service prng_seeder /system/bin/prng_seeder
+ user prng_seeder
+ group prng_seeder
+ stdio_to_kmsg
+ socket prng_seeder stream+listen 0666 prng_seeder prng_seeder
diff --git a/prng_seeder/src/conditioner.rs b/prng_seeder/src/conditioner.rs
new file mode 100644
index 00000000..eca8af88
--- /dev/null
+++ b/prng_seeder/src/conditioner.rs
@@ -0,0 +1,76 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::{fs::File, io::Read, os::unix::io::AsRawFd};
+
+use anyhow::{ensure, Context, Result};
+use log::debug;
+use nix::fcntl::{fcntl, FcntlArg::F_SETFL, OFlag};
+use tokio::io::AsyncReadExt;
+
+use crate::drbg;
+
+const SEED_FOR_CLIENT_LEN: usize = 496;
+const NUM_REQUESTS_PER_RESEED: u32 = 256;
+
+pub struct ConditionerBuilder {
+ hwrng: File,
+ rg: drbg::Drbg,
+}
+
+impl ConditionerBuilder {
+ pub fn new(mut hwrng: File) -> Result<ConditionerBuilder> {
+ let mut et: drbg::Entropy = [0; drbg::ENTROPY_LEN];
+ hwrng.read_exact(&mut et).context("hwrng.read_exact in new")?;
+ let rg = drbg::Drbg::new(&et)?;
+ fcntl(hwrng.as_raw_fd(), F_SETFL(OFlag::O_NONBLOCK))
+ .context("setting O_NONBLOCK on hwrng")?;
+ Ok(ConditionerBuilder { hwrng, rg })
+ }
+
+ pub fn build(self) -> Conditioner {
+ Conditioner {
+ hwrng: tokio::fs::File::from_std(self.hwrng),
+ rg: self.rg,
+ requests_since_reseed: 0,
+ }
+ }
+}
+
+pub struct Conditioner {
+ hwrng: tokio::fs::File,
+ rg: drbg::Drbg,
+ requests_since_reseed: u32,
+}
+
+impl Conditioner {
+ pub async fn reseed_if_necessary(&mut self) -> Result<()> {
+ if self.requests_since_reseed >= NUM_REQUESTS_PER_RESEED {
+ debug!("Reseeding DRBG");
+ let mut et: drbg::Entropy = [0; drbg::ENTROPY_LEN];
+ self.hwrng.read_exact(&mut et).await.context("hwrng.read_exact in reseed")?;
+ self.rg.reseed(&et)?;
+ self.requests_since_reseed = 0;
+ }
+ Ok(())
+ }
+
+ pub fn request(&mut self) -> Result<[u8; SEED_FOR_CLIENT_LEN]> {
+ ensure!(self.requests_since_reseed < NUM_REQUESTS_PER_RESEED, "Not enough reseeds");
+ let mut seed_for_client = [0u8; SEED_FOR_CLIENT_LEN];
+ self.rg.generate(&mut seed_for_client)?;
+ self.requests_since_reseed += 1;
+ Ok(seed_for_client)
+ }
+}
diff --git a/prng_seeder/src/cutils_socket.rs b/prng_seeder/src/cutils_socket.rs
new file mode 100644
index 00000000..ab2c8698
--- /dev/null
+++ b/prng_seeder/src/cutils_socket.rs
@@ -0,0 +1,25 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::ffi::CString;
+use std::os::unix::{net::UnixListener, prelude::FromRawFd};
+
+use anyhow::{ensure, Result};
+
+pub fn android_get_control_socket(name: &str) -> Result<UnixListener> {
+ let name = CString::new(name)?;
+ let fd = unsafe { cutils_socket_bindgen::android_get_control_socket(name.as_ptr()) };
+ ensure!(fd >= 0, "android_get_control_socket failed");
+ Ok(unsafe { UnixListener::from_raw_fd(fd) })
+}
diff --git a/prng_seeder/src/drbg.rs b/prng_seeder/src/drbg.rs
new file mode 100644
index 00000000..89c5a888
--- /dev/null
+++ b/prng_seeder/src/drbg.rs
@@ -0,0 +1,65 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use anyhow::{ensure, Result};
+use bssl_ffi as bssl_sys;
+
+pub const ENTROPY_LEN: usize = bssl_sys::CTR_DRBG_ENTROPY_LEN as usize;
+
+pub type Entropy = [u8; ENTROPY_LEN];
+
+pub struct Drbg(*mut bssl_sys::CTR_DRBG_STATE);
+
+impl Drbg {
+ pub fn new(entropy: &Entropy) -> Result<Drbg> {
+ let p = unsafe { bssl_sys::CTR_DRBG_new(entropy.as_ptr(), std::ptr::null(), 0) };
+ ensure!(!p.is_null(), "CTR_DRBG_new failed");
+ Ok(Drbg(p))
+ }
+
+ pub fn reseed(&mut self, entropy: &Entropy) -> Result<()> {
+ ensure!(
+ unsafe { bssl_sys::CTR_DRBG_reseed(self.0, entropy.as_ptr(), std::ptr::null(), 0) }
+ == 1,
+ "CTR_DRBG_reseed failed"
+ );
+ Ok(())
+ }
+
+ pub fn generate(&mut self, buf: &mut [u8]) -> Result<()> {
+ ensure!(
+ unsafe {
+ bssl_sys::CTR_DRBG_generate(
+ self.0,
+ buf.as_mut_ptr(),
+ buf.len(),
+ std::ptr::null(),
+ 0,
+ )
+ } == 1,
+ "CTR_DRBG_generate failed"
+ );
+ Ok(())
+ }
+}
+
+impl Drop for Drbg {
+ fn drop(&mut self) {
+ unsafe {
+ bssl_sys::CTR_DRBG_free(self.0);
+ }
+ }
+}
+
+unsafe impl Send for Drbg {}
diff --git a/prng_seeder/src/main.rs b/prng_seeder/src/main.rs
new file mode 100644
index 00000000..10f853dd
--- /dev/null
+++ b/prng_seeder/src/main.rs
@@ -0,0 +1,136 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! FIPS compliant random number conditioner. Reads from /dev/hw_random
+//! and applies the NIST SP 800-90A CTR DRBG strategy to provide
+//! pseudorandom bytes to clients which connect to a socket provided
+//! by init.
+
+mod conditioner;
+mod cutils_socket;
+mod drbg;
+
+use std::{
+ convert::Infallible,
+ fs::remove_file,
+ io::ErrorKind,
+ os::unix::net::UnixListener,
+ path::{Path, PathBuf},
+};
+
+use anyhow::{ensure, Context, Result};
+use log::{error, info, Level};
+use nix::sys::signal;
+use tokio::{io::AsyncWriteExt, net::UnixListener as TokioUnixListener};
+
+use crate::conditioner::ConditionerBuilder;
+
+//#[derive(Debug, clap::Parser)]
+struct Cli {
+ //#[clap(long, default_value = "/dev/hw_random")]
+ source: PathBuf,
+ //#[clap(long)]
+ socket: Option<PathBuf>,
+}
+
+fn configure_logging() -> Result<()> {
+ ensure!(
+ logger::init(
+ logger::Config::default().with_tag_on_device("prng_seeder").with_min_level(Level::Info)
+ ),
+ "log configuration failed"
+ );
+ Ok(())
+}
+
+fn get_socket(path: &Path) -> Result<UnixListener> {
+ if let Err(e) = remove_file(path) {
+ if e.kind() != ErrorKind::NotFound {
+ return Err(e).context(format!("Removing old socket: {}", path.display()));
+ }
+ } else {
+ info!("Deleted old {}", path.display());
+ }
+ UnixListener::bind(path)
+ .with_context(|| format!("In get_socket: binding socket to {}", path.display()))
+}
+
+fn setup() -> Result<(ConditionerBuilder, UnixListener)> {
+ configure_logging()?;
+ let cli = Cli { source: PathBuf::from("/dev/hw_random"), socket: None };
+ unsafe { signal::signal(signal::Signal::SIGPIPE, signal::SigHandler::SigIgn) }
+ .context("In setup, setting SIGPIPE to SIG_IGN")?;
+
+ let listener = match cli.socket {
+ Some(path) => get_socket(path.as_path())?,
+ None => cutils_socket::android_get_control_socket("prng_seeder")
+ .context("In setup, calling android_get_control_socket")?,
+ };
+ let hwrng = std::fs::File::open(&cli.source)
+ .with_context(|| format!("Unable to open hwrng {}", cli.source.display()))?;
+ let cb = ConditionerBuilder::new(hwrng)?;
+ Ok((cb, listener))
+}
+
+async fn listen_loop(cb: ConditionerBuilder, listener: UnixListener) -> Result<Infallible> {
+ let mut conditioner = cb.build();
+ listener.set_nonblocking(true).context("In listen_loop, on set_nonblocking")?;
+ let listener = TokioUnixListener::from_std(listener).context("In listen_loop, on from_std")?;
+ info!("Starting listen loop");
+ loop {
+ match listener.accept().await {
+ Ok((mut stream, _)) => {
+ let new_bytes = conditioner.request()?;
+ tokio::spawn(async move {
+ if let Err(e) = stream.write_all(&new_bytes).await {
+ error!("Request failed: {}", e);
+ }
+ });
+ conditioner.reseed_if_necessary().await?;
+ }
+ Err(e) if e.kind() == ErrorKind::Interrupted => {}
+ Err(e) => return Err(e).context("accept on socket failed"),
+ }
+ }
+}
+
+fn run() -> Result<Infallible> {
+ let (cb, listener) = match setup() {
+ Ok(t) => t,
+ Err(e) => {
+ // If setup fails, just hang forever. That way init doesn't respawn us.
+ error!("Hanging forever because setup failed: {:?}", e);
+ // Logs are sometimes mysteriously not being logged, so print too
+ println!("prng_seeder: Hanging forever because setup failed: {:?}", e);
+ loop {
+ std::thread::park();
+ error!("std::thread::park() finished unexpectedly, re-parking thread");
+ }
+ }
+ };
+
+ tokio::runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .context("In run, building reactor")?
+ .block_on(async { listen_loop(cb, listener).await })
+}
+
+fn main() {
+ let e = run();
+ error!("Launch terminated: {:?}", e);
+ // Logs are sometimes mysteriously not being logged, so print too
+ println!("prng_seeder: launch terminated: {:?}", e);
+ std::process::exit(-1);
+}