1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
//! Low-level Compute Secret Store interfaces.
use crate::abi;
use bytes::{Bytes, BytesMut};
use fastly_shared::FastlyStatus;
/// A low-level interface to Secret Store.
///
/// Methods here are typically exposed as the `try_*` APIs on
/// [`crate::secret_store::SecretStore`] and should not be needed directly.
#[derive(Debug, Eq, Hash, PartialEq)]
#[repr(transparent)]
pub struct SecretStoreHandle {
handle: u32,
}
impl SecretStoreHandle {
/// An invalid secret store handle.
pub const INVALID: Self = SecretStoreHandle {
handle: fastly_shared::INVALID_SECRET_STORE_HANDLE,
};
/// Acquire a handle to a Secret Store.
///
/// If a handle could not be acquired, an [`OpenError`] will be returned.
pub fn open(secret_store_name: &str) -> Result<Self, OpenError> {
use OpenError::*;
let mut handle = Self::INVALID;
unsafe {
abi::fastly_secret_store::open(
secret_store_name.as_ptr(),
secret_store_name.len(),
handle.as_u32_mut(),
)
}
.result()
.map(|_| handle)
.map_err(|s| match s {
// If we receive a `None` code, there was no store with this name.
FastlyStatus::NONE => SecretStoreDoesNotExist(secret_store_name.to_string()),
// If we receive an `INVAL` code, the given name was not a valid store name.
FastlyStatus::INVAL => InvalidSecretStoreName(secret_store_name.to_string()),
_ => Unexpected(s),
})
}
/// Lookup a secret in this Secret Store, and return a handle to it.
///
/// If successful, this function returns `Ok(Some(_))` if a secret was found, or `Ok(None)` if
/// no secret with the given name was found. If the lookup failed, a [`LookupError`] will be
/// returned.
pub fn get(&self, secret_name: &str) -> Result<Option<SecretHandle>, LookupError> {
use LookupError::*;
let mut handle = fastly_shared::INVALID_SECRET_HANDLE;
let status = unsafe {
abi::fastly_secret_store::get(
self.as_u32(),
secret_name.as_ptr(),
secret_name.len(),
&mut handle,
)
};
match status {
FastlyStatus::OK => Ok(Some(SecretHandle { handle })),
FastlyStatus::NONE => Ok(None),
FastlyStatus::BADF => Err(InvalidSecretStoreHandle),
FastlyStatus::INVAL => Err(InvalidSecretName(secret_name.to_string())),
_ => Err(Unexpected(status)),
}
}
/// Return true if the secret store contains a secret with the given name.
pub fn contains(&self, name: &str) -> Result<bool, LookupError> {
match self.get(name) {
Ok(Some(_)) => Ok(true),
Ok(None) => Ok(false),
Err(e) => Err(e),
}
}
/// Get the underlying representation of the handle.
///
/// This should only be used when calling the raw ABI directly, and care should be taken not to
/// reuse or alias handle values.
pub(crate) fn as_u32(&self) -> u32 {
self.handle
}
/// Get a mutable reference to the underlying `u32` representation of the handle.
///
/// This should only be used when calling the raw ABI directly, and care should be taken not to
/// reuse or alias handle values.
pub(crate) fn as_u32_mut(&mut self) -> &mut u32 {
&mut self.handle
}
}
/// A low-level interface to a secret.
///
/// Methods here are typically exposed as the APIs on
/// [`crate::secret_store::Secret`] and should not be needed directly.
#[derive(Debug, Eq, Hash, PartialEq)]
#[repr(transparent)]
pub struct SecretHandle {
handle: u32,
}
impl SecretHandle {
/// An invalid secret handle.
pub const INVALID: Self = SecretHandle {
handle: fastly_shared::INVALID_SECRET_HANDLE,
};
/// Return the plaintext value of this secret.
pub fn plaintext(&self) -> Result<Bytes, DecryptError> {
const INITIAL_SECRET_PLAINTEXT_BUF_SIZE: usize = 1024;
if self.is_invalid() {
panic!("cannot lookup plaintext with invalid secret handle");
}
// Allocate a mutable byte buffer for our secret's contents.
let mut plaintext_buf = BytesMut::zeroed(INITIAL_SECRET_PLAINTEXT_BUF_SIZE);
let mut nwritten = 0usize;
// Attempt to read the secret's plaintext contents into the buffer.
let status = unsafe {
abi::fastly_secret_store::plaintext(
self.as_u32(),
plaintext_buf.as_mut_ptr(),
plaintext_buf.len(),
&mut nwritten,
)
};
// If the provided buffer was not large enough to fit the plaintext, reserve additional
// capacity observing the number of bytes needed, in the `nwritten` value. Then, set
// `nwritten` back to 0 and try again.
let status = match status {
FastlyStatus::BUFLEN if nwritten != 0 => {
plaintext_buf.resize(nwritten, 0);
nwritten = 0;
unsafe {
abi::fastly_secret_store::plaintext(
self.as_u32(),
plaintext_buf.as_mut_ptr(),
plaintext_buf.len(),
&mut nwritten,
)
}
}
s => s,
};
match status.result() {
Ok(()) => {
// Freeze the bytes, being sure to set the length to reflect the number of bytes
// written into the buffer by the host.
unsafe {
plaintext_buf.set_len(nwritten);
}
Ok(plaintext_buf.freeze())
}
Err(status) => Err(DecryptError::Unexpected(status)),
}
}
/// Create a new "secret" from the given memory. This is not the suggested way to create
/// [`Secret`]s; instead, we suggest using [`SecretStore::get`].
///
/// This method can be used for data that should be secret, but is being obtained by
/// some other means than the secret store. New "secrets" created this way use plaintext
/// only, and live in the session's memory unencrypted for much longer than secrets
/// generated by [`SecretStore::get`]. They should thus only be used in situations in
/// which an API requires a [`Secret`], but you cannot (for whatever reason) use a
/// [`SecretStore`] to store them.
pub fn new(secret: &[u8]) -> Result<SecretHandle, FastlyStatus> {
let len = secret.len();
if len > (64 * 1024) {
return Err(FastlyStatus::INVAL);
}
let ptr = secret.as_ptr();
let mut handle = fastly_shared::INVALID_SECRET_HANDLE;
let res = unsafe { fastly_sys::fastly_secret_store::from_bytes(ptr, len, &mut handle) };
if res != FastlyStatus::OK {
return Err(res);
}
if handle == fastly_shared::INVALID_SECRET_HANDLE {
return Err(FastlyStatus::ERROR);
}
Ok(SecretHandle { handle })
}
/// Return true if the [`crate::secret_store::Secret`] handle is invalid.
pub fn is_invalid(&self) -> bool {
self.handle == Self::INVALID.handle
}
/// Get the underlying representation of the handle.
///
/// This should only be used when calling the raw ABI directly, and care should be taken not to
/// reuse or alias handle values.
pub(crate) fn as_u32(&self) -> u32 {
self.handle
}
}
/// Errors thrown when a secret store could not be opened.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum OpenError {
/// No secret store exists with the given name.
#[error("secret store could not be found: {0}")]
SecretStoreDoesNotExist(String),
/// The secret store name given is invalid.
#[error("invalid secret store name: {0}")]
InvalidSecretStoreName(String),
/// An unexpected error occurred
#[error("unexpected error: {0:?}")]
Unexpected(FastlyStatus),
}
/// Errors thrown when a secret store lookup failed.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum LookupError {
/// The secret store handle given is invalid.
#[error("invalid secret store handle")]
InvalidSecretStoreHandle,
/// The secret name given is invalid.
#[error("invalid secret name: {0}")]
InvalidSecretName(String),
/// The secret handle given is invalid.
#[error("invalid secret handle")]
InvalidSecretHandle,
/// An unexpected error occurred.
#[error("unexpected error: {0:?}")]
Unexpected(FastlyStatus),
}
/// Errors thrown when decrypting a secret failed.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum DecryptError {
/// An unexpected error occurred.
#[error("unexpected error: {0:?}")]
Unexpected(FastlyStatus),
}