oberon/lib.rs
1/*
2 Copyright Michael Lodder. All Rights Reserved.
3 SPDX-License-Identifier: Apache-2.0
4*/
5//! Oberon allows endpoints to issue multi-factor capable tokens to consumers
6//! who can prove their validity with disclosing the tokens themselves without requiring
7//! email, SMS, or authenticator apps. Endpoints
8//! only need to store a single public key and not any tokens. An attacker that breaks
9//! into the server doesn't have any password/token files to steal and only would see
10//! a public key. The proof of token validity is only 96 bytes while the token itself
11//! is only 48 bytes. The issuing party and verifying servers can be separate entities.
12//!
13//! Tokens are created by signing an identitifier with a secret key.
14//!
15//! Tokens are presented to a verifier as a zero-knowledge proof such that
16//! the verifier never learns the value of the token. Additional blindings
17//! can be applied to the token so additional security factors are required
18//! before using it. One example is a pin or password.
19//!
20//! ```
21//! use oberon::*;
22//! use rand::thread_rng;
23//!
24//! let sk = SecretKey::hash(b"my super secret key seed");
25//! let pk = PublicKey::from(&sk);
26//! let id = b"test identity";
27//! let token = sk.sign(id).unwrap();
28//! let blinding = Blinding::new(b"<your passcode>");
29//! let blinded_token = token - &blinding;
30//!
31//! let timestamp = [0x00, 0x05, 0xc0, 0xba, 0xea, 0x9c, 0x82, 0xb0];
32//!
33//! match Proof::new(&blinded_token, &[blinding], id, ×tamp, thread_rng()) {
34//! None => panic!(""),
35//! Some(proof) => {
36//! assert_eq!(proof.open(pk, id, ×tamp).unwrap_u8(), 1u8);
37//! }
38//! }
39//! ```
40//!
41//! This crate supports no-std by default. As such this means only 2 additional factors
42//! can be used. This is usually not a problem since 3FA is good enough. If you need
43//! more than 3FA (what security context are in???) this can be done with some work
44//! as is described below.
45//!
46//! Blinding factors are applied to tokens as follows
47//!
48//! ```
49//! use oberon::{SecretKey, Token, Blinding};
50//!
51//! let sk = SecretKey::hash(b"my super secret key seed");
52//! let token = sk.sign(b"test identity").unwrap();
53//!
54//! let blinded_token = token - Blinding::new(b"<your pin number>");
55//! let blinded_token = blinded_token - Blinding::new(b"<another factor like HSM key>");
56//! let blinded_token = blinded_token - Blinding::new(b"<another factor like ENV>");
57//! ```
58//!
59//! It is important that the blindings are subtracted and not added since addition is used
60//! by `Proof::new`
61//!
62//! This scenario uses 3 extra blindings. In no-std mode, only two can be passed
63//! to `Proof::new`. In order to apply the third and still have this work, you simply
64//! reverse all but two of the blindings by adding them back in.
65//! This restriction doesn't apply when `alloc` or `std` features are used.
66//!
67//! This crate also supports compiling to wasm. Make sure to use --features=wasm
68//! to get the necessary functions
69
70#![no_std]
71#![deny(
72 warnings,
73 missing_docs,
74 unused_import_braces,
75 unused_qualifications,
76 trivial_casts,
77 trivial_numeric_casts
78)]
79#![cfg_attr(docsrs, feature(doc_cfg))]
80
81#[cfg(feature = "alloc")]
82extern crate alloc;
83#[cfg(feature = "std")]
84extern crate std;
85
86#[cfg(feature = "wasm")]
87macro_rules! wasm_slice_impl {
88 ($name:ident) => {
89 impl wasm_bindgen::describe::WasmDescribe for $name {
90 fn describe() {
91 wasm_bindgen::describe::inform(wasm_bindgen::describe::SLICE)
92 }
93 }
94
95 impl wasm_bindgen::convert::IntoWasmAbi for $name {
96 type Abi = wasm_bindgen::convert::WasmSlice;
97
98 fn into_abi(self) -> Self::Abi {
99 let a = self.to_bytes();
100 Self::Abi {
101 ptr: a.as_ptr().into_abi(),
102 len: a.len() as u32,
103 }
104 }
105 }
106
107 impl wasm_bindgen::convert::FromWasmAbi for $name {
108 type Abi = wasm_bindgen::convert::WasmSlice;
109
110 #[inline]
111 unsafe fn from_abi(js: Self::Abi) -> Self {
112 use core::{convert::TryFrom, slice};
113
114 let ptr = <*mut u8>::from_abi(js.ptr);
115 let len = js.len as usize;
116 let r = slice::from_raw_parts(ptr, len);
117
118 match <[u8; $name::BYTES]>::try_from(r) {
119 Ok(d) => $name::from_bytes(&d).unwrap(),
120 Err(_) => Self::default(),
121 }
122 }
123 }
124
125 impl wasm_bindgen::convert::OptionIntoWasmAbi for $name {
126 fn none() -> wasm_bindgen::convert::WasmSlice {
127 wasm_bindgen::convert::WasmSlice { ptr: 0, len: 0 }
128 }
129 }
130
131 impl wasm_bindgen::convert::OptionFromWasmAbi for $name {
132 fn is_none(slice: &wasm_bindgen::convert::WasmSlice) -> bool {
133 slice.ptr == 0
134 }
135 }
136
137 impl TryFrom<wasm_bindgen::JsValue> for $name {
138 type Error = &'static str;
139
140 fn try_from(value: wasm_bindgen::JsValue) -> Result<Self, Self::Error> {
141 serde_json::from_str::<$name>(&value.as_string().unwrap())
142 .map_err(|_| "unable to deserialize value")
143 }
144 }
145 };
146}
147
148mod blinding;
149#[cfg(feature = "ffi")]
150mod ffi;
151#[cfg(feature = "php")]
152mod php;
153mod proof;
154mod public_key;
155#[cfg(feature = "python")]
156mod python;
157mod secret_key;
158mod token;
159mod util;
160#[cfg(feature = "wasm")]
161mod web;
162
163#[cfg(not(any(feature = "rust", feature = "alloc", feature = "blstrs_plus")))]
164compile_error!("Please select bls12_381_plus or blstrs_plus as your elliptic curve");
165
166/// The inner representation types
167pub mod inner_types {
168 #[cfg(not(feature = "std"))]
169 pub use bls12_381_plus::{
170 elliptic_curve,
171 ff::{Field, PrimeField},
172 group::{self, Curve, Group, GroupEncoding, prime::PrimeCurveAffine},
173 *
174 };
175 #[cfg(feature = "std")]
176 pub use blstrs_plus::{
177 elliptic_curve,
178 ff::{Field, PrimeField},
179 group::{self, Curve, Group, GroupEncoding, prime::PrimeCurveAffine},
180 pairing_lib::{self, MillerLoopResult, MultiMillerLoop},
181 *
182 };
183}
184
185pub use blinding::*;
186#[cfg_attr(docsrs, doc(cfg(feature = "ffi")))]
187#[cfg(feature = "ffi")]
188pub use ffi::*;
189#[cfg_attr(docsrs, doc(cfg(feature = "php")))]
190#[cfg(feature = "php")]
191pub use php::*;
192pub use proof::*;
193pub use public_key::*;
194#[cfg_attr(docsrs, doc(cfg(feature = "python")))]
195#[cfg(feature = "python")]
196pub use python::*;
197pub use secret_key::*;
198pub use token::*;
199#[cfg_attr(docsrs, doc(cfg(feature = "wasm")))]
200#[cfg(feature = "wasm")]
201pub use web::*;