blob: 85dc6c9d18017792ac188809758cd56a8069cda5 [file] [log] [blame]
Pierre-Clément Tosi90cd4f12023-02-17 11:19:56 +00001// Copyright 2023, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Wrapper around BoringSSL/OpenSSL symbols.
16
17use core::convert::AsRef;
18use core::ffi::{c_char, c_int, CStr};
19use core::fmt;
20use core::mem::MaybeUninit;
21use core::num::NonZeroU32;
22use core::ptr;
23
24use bssl_ffi::ERR_get_error_line;
25use bssl_ffi::ERR_lib_error_string;
26use bssl_ffi::ERR_reason_error_string;
27use bssl_ffi::EVP_AEAD_CTX_aead;
28use bssl_ffi::EVP_AEAD_CTX_init;
29use bssl_ffi::EVP_AEAD_CTX_open;
30use bssl_ffi::EVP_AEAD_CTX_seal;
31use bssl_ffi::EVP_AEAD_max_overhead;
32use bssl_ffi::EVP_aead_aes_256_gcm_randnonce;
33use bssl_ffi::EVP_sha512;
34use bssl_ffi::EVP_AEAD;
35use bssl_ffi::EVP_AEAD_CTX;
36use bssl_ffi::HKDF;
37
38#[derive(Debug)]
39pub struct Error {
40 packed: NonZeroU32,
41 file: Option<&'static CStr>,
42 line: c_int,
43}
44
45impl Error {
46 fn get() -> Option<Self> {
47 let mut file = MaybeUninit::uninit();
48 let mut line = MaybeUninit::uninit();
49 // SAFETY - The function writes to the provided pointers, validated below.
50 let packed = unsafe { ERR_get_error_line(file.as_mut_ptr(), line.as_mut_ptr()) };
51 // SAFETY - Any possible value returned could be considered a valid *const c_char.
52 let file = unsafe { file.assume_init() };
53 // SAFETY - Any possible value returned could be considered a valid c_int.
54 let line = unsafe { line.assume_init() };
55
56 let packed = packed.try_into().ok()?;
57 // SAFETY - Any non-NULL result is expected to point to a global const C string.
58 let file = unsafe { as_static_cstr(file) };
59
60 Some(Self { packed, file, line })
61 }
62
63 fn packed_value(&self) -> u32 {
64 self.packed.get()
65 }
66
67 fn library_name(&self) -> Option<&'static CStr> {
68 // SAFETY - Call to a pure function.
69 let name = unsafe { ERR_lib_error_string(self.packed_value()) };
70 // SAFETY - Any non-NULL result is expected to point to a global const C string.
71 unsafe { as_static_cstr(name) }
72 }
73
74 fn reason(&self) -> Option<&'static CStr> {
75 // SAFETY - Call to a pure function.
76 let reason = unsafe { ERR_reason_error_string(self.packed_value()) };
77 // SAFETY - Any non-NULL result is expected to point to a global const C string.
78 unsafe { as_static_cstr(reason) }
79 }
80}
81
82impl fmt::Display for Error {
83 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
84 let unknown_library = CStr::from_bytes_with_nul(b"{unknown library}\0").unwrap();
85 let unknown_reason = CStr::from_bytes_with_nul(b"{unknown reason}\0").unwrap();
86 let unknown_file = CStr::from_bytes_with_nul(b"??\0").unwrap();
87
88 let packed = self.packed_value();
89 let library = self.library_name().unwrap_or(unknown_library).to_str().unwrap();
90 let reason = self.reason().unwrap_or(unknown_reason).to_str().unwrap();
91 let file = self.file.unwrap_or(unknown_file).to_str().unwrap();
92 let line = self.line;
93
94 write!(f, "{file}:{line}: {library}: {reason} ({packed:#x})")
95 }
96}
97
98#[derive(Copy, Clone)]
99pub struct ErrorIterator {}
100
101impl Iterator for ErrorIterator {
102 type Item = Error;
103
104 fn next(&mut self) -> Option<Self::Item> {
105 Self::Item::get()
106 }
107}
108
109pub type Result<T> = core::result::Result<T, ErrorIterator>;
110
111#[repr(transparent)]
112pub struct Aead(EVP_AEAD);
113
114impl Aead {
115 pub fn aes_256_gcm_randnonce() -> Option<&'static Self> {
116 // SAFETY - Returned pointer is checked below.
117 let aead = unsafe { EVP_aead_aes_256_gcm_randnonce() };
118 if aead.is_null() {
119 None
120 } else {
121 // SAFETY - We assume that the non-NULL value points to a valid and static EVP_AEAD.
122 Some(unsafe { &*(aead as *const _) })
123 }
124 }
125
126 pub fn max_overhead(&self) -> usize {
127 // SAFETY - Function should only read from self.
128 unsafe { EVP_AEAD_max_overhead(self.as_ref() as *const _) }
129 }
130}
131
132#[repr(transparent)]
133pub struct AeadCtx(EVP_AEAD_CTX);
134
135impl AeadCtx {
136 pub fn new_aes_256_gcm_randnonce(key: &[u8]) -> Result<Self> {
137 let aead = Aead::aes_256_gcm_randnonce().unwrap();
138
139 Self::new(aead, key)
140 }
141
142 fn new(aead: &'static Aead, key: &[u8]) -> Result<Self> {
143 const DEFAULT_TAG_LENGTH: usize = 0;
144 let engine = ptr::null_mut(); // Use default implementation.
145 let mut ctx = MaybeUninit::zeroed();
146 // SAFETY - Initialize the EVP_AEAD_CTX with const pointers to the AEAD and key.
147 let result = unsafe {
148 EVP_AEAD_CTX_init(
149 ctx.as_mut_ptr(),
150 aead.as_ref() as *const _,
151 key.as_ptr(),
152 key.len(),
153 DEFAULT_TAG_LENGTH,
154 engine,
155 )
156 };
157
158 if result == 1 {
159 // SAFETY - We assume that the non-NULL value points to a valid and static EVP_AEAD.
160 Ok(Self(unsafe { ctx.assume_init() }))
161 } else {
162 Err(ErrorIterator {})
163 }
164 }
165
166 pub fn aead(&self) -> Option<&'static Aead> {
167 // SAFETY - The function should only read from self.
168 let aead = unsafe { EVP_AEAD_CTX_aead(self.as_ref() as *const _) };
169 if aead.is_null() {
170 None
171 } else {
172 // SAFETY - We assume that the non-NULL value points to a valid and static EVP_AEAD.
173 Some(unsafe { &*(aead as *const _) })
174 }
175 }
176
177 pub fn open<'b>(&self, out: &'b mut [u8], data: &[u8]) -> Result<&'b mut [u8]> {
178 let nonce = ptr::null_mut();
179 let nonce_len = 0;
180 let ad = ptr::null_mut();
181 let ad_len = 0;
182 let mut out_len = MaybeUninit::uninit();
183 // SAFETY - The function should only read from self and write to out (at most the provided
184 // number of bytes) and out_len while reading from data (at most the provided number of
185 // bytes), ignoring any NULL input.
186 let result = unsafe {
187 EVP_AEAD_CTX_open(
188 self.as_ref() as *const _,
189 out.as_mut_ptr(),
190 out_len.as_mut_ptr(),
191 out.len(),
192 nonce,
193 nonce_len,
194 data.as_ptr(),
195 data.len(),
196 ad,
197 ad_len,
198 )
199 };
200
201 if result == 1 {
202 // SAFETY - Any value written to out_len could be a valid usize. The value itself is
203 // validated as being a proper slice length by panicking in the following indexing
204 // otherwise.
205 let out_len = unsafe { out_len.assume_init() };
206 Ok(&mut out[..out_len])
207 } else {
208 Err(ErrorIterator {})
209 }
210 }
211
212 pub fn seal<'b>(&self, out: &'b mut [u8], data: &[u8]) -> Result<&'b mut [u8]> {
213 let nonce = ptr::null_mut();
214 let nonce_len = 0;
215 let ad = ptr::null_mut();
216 let ad_len = 0;
217 let mut out_len = MaybeUninit::uninit();
218 // SAFETY - The function should only read from self and write to out (at most the provided
219 // number of bytes) while reading from data (at most the provided number of bytes),
220 // ignoring any NULL input.
221 let result = unsafe {
222 EVP_AEAD_CTX_seal(
223 self.as_ref() as *const _,
224 out.as_mut_ptr(),
225 out_len.as_mut_ptr(),
226 out.len(),
227 nonce,
228 nonce_len,
229 data.as_ptr(),
230 data.len(),
231 ad,
232 ad_len,
233 )
234 };
235
236 if result == 1 {
237 // SAFETY - Any value written to out_len could be a valid usize. The value itself is
238 // validated as being a proper slice length by panicking in the following indexing
239 // otherwise.
240 let out_len = unsafe { out_len.assume_init() };
241 Ok(&mut out[..out_len])
242 } else {
243 Err(ErrorIterator {})
244 }
245 }
246}
247
248/// Cast a C string pointer to a static non-mutable reference.
249///
250/// # Safety
251///
252/// The caller needs to ensure that the pointer points to a valid C string and that the C lifetime
253/// of the string is compatible with a static Rust lifetime.
254unsafe fn as_static_cstr(p: *const c_char) -> Option<&'static CStr> {
255 if p.is_null() {
256 None
257 } else {
258 Some(CStr::from_ptr(p))
259 }
260}
261
262impl AsRef<EVP_AEAD> for Aead {
263 fn as_ref(&self) -> &EVP_AEAD {
264 &self.0
265 }
266}
267
268impl AsRef<EVP_AEAD_CTX> for AeadCtx {
269 fn as_ref(&self) -> &EVP_AEAD_CTX {
270 &self.0
271 }
272}
273
274pub fn hkdf_sh512<const N: usize>(secret: &[u8], salt: &[u8], info: &[u8]) -> Result<[u8; N]> {
275 let mut key = [0; N];
276 // SAFETY - The function shouldn't access any Rust variable and the returned value is accepted
277 // as a potentially NULL pointer.
278 let digest = unsafe { EVP_sha512() };
279
280 assert!(!digest.is_null());
281 // SAFETY - Only reads from/writes to the provided slices and supports digest was checked not
282 // be NULL.
283 let result = unsafe {
284 HKDF(
285 key.as_mut_ptr(),
286 key.len(),
287 digest,
288 secret.as_ptr(),
289 secret.len(),
290 salt.as_ptr(),
291 salt.len(),
292 info.as_ptr(),
293 info.len(),
294 )
295 };
296
297 if result == 1 {
298 Ok(key)
299 } else {
300 Err(ErrorIterator {})
301 }
302}