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, &timestamp, thread_rng()) {
34//!     None => panic!(""),
35//!     Some(proof) => {
36//!         assert_eq!(proof.open(pk, id, &timestamp).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::*;