rfc2289_otp/
lib.rs

1//! # RFC 2289 One-Time Password
2//! 
3//! Implements the One-Time Password (OTP) algorithm described in
4//! [IETF RFC 2289](https://www.rfc-editor.org/rfc/rfc2289.html), including
5//! functions for parsing strings and mapping between dictionary words and OTP
6//! values.
7//! 
8//! This algorithm is **NOT** the same as the TOTP and HOTP algorithms widely in use
9//! today for multifactor authentication: these are defined in other RFCs. This
10//! algorithm, however, is used in the `OTP` SASL mechanism as described in
11//! [IETF RFC 2444](https://www.rfc-editor.org/rfc/rfc2444.html).
12//! 
13//! ## Security
14//!
15//! Note that there are only three hash algorithms defined for use with this
16//! algorithm, **all of which are no longer considered secure**. These are:
17//!
18//! - MD4
19//! - MD5
20//! - SHA1
21//!
22//! **However**, I am of the non-professional opinion that these algorithms are
23//! generally fine for the way that they are used by the OTP algorithm, because the
24//! OTP algorithm performs the hash a fixed number of times with a fixed seed and a
25//! passphrase. Still, I **highly** recommend using the `sha1` algorithm
26//! exclusively. It is the newest and most secure of the three.
27//!
28//! If more algorithms are ever made official, you should see the new algorithms
29//! [here](https://www.iana.org/assignments/otp-parameters/otp-parameters.xhtml).
30//!
31//! ## Feature Flags
32//!
33//! The feature flags for this library are:
34//!
35//! - `md4`: MD4 support
36//! - `md5`: MD5 support
37//! - `sha1`: SHA1 support
38//! - `words`: Translation to and from dictionary words
39//! - `dyndig`: Support for any digest that implements `digest::DynDigest`
40//! - `parsing`: Parsing OTP strings
41//!
42//! All of the above are enabled by default.
43//!
44//! ## Usage
45//!
46//! Let's say you receive an OTP challenge as a string like so:
47//!
48//! ```text
49//! otp-md5 499 ke1234 ext
50//! ```
51//!
52//! Decode this string like so:
53//!
54//! ```rust
55//! let challenge_str = "otp-md5 487 dog2";
56//! let challenge = rfc2289_otp::parse_otp_challenge(challenge_str).unwrap();
57//! ```
58//!
59//! If it is a valid string, you should get a data structure that looks like this:
60//!
61//! ```rust
62//! pub struct OTPChallenge <'a> {
63//!     pub hash_alg: &'a str,
64//!     pub hash_count: usize,
65//!     pub seed: &'a str,
66//! }
67//! ```
68//!
69//! You can use this data structure to calculate the OTP like so:
70//!
71//! ```rust
72//! let challenge = rfc2289_otp::OTPChallenge {
73//!     hash_alg: "md5",
74//!     hash_count: 200,
75//!     seed: "wibby123",
76//! };
77//! let extremely_secure_passphrase = "banana";
78//! let otp = rfc2289_otp::calculate_otp(
79//!     challenge.hash_alg,
80//!     extremely_secure_passphrase,
81//!     challenge.seed,
82//!     challenge.hash_count,
83//!     None,
84//! ).unwrap();
85//! ```
86//!
87//! If the algorithm was understood, and there wasn't any other problem, you should
88//! get a `[u8; 8]` back (64-bits), which is your OTP value.
89//!
90//! You can directly convert this value to hex, and prepend `hex:` to it to produce
91//! a valid OTP response.
92//!
93//! Or, you can convert it to dictionary words using the standard dictionary defined
94//! in the specification using `convert_to_word_format`. Join these words with
95//! spaces and prefix it with `word:`.
96//!
97//! If implementing an OTP server, you can parse these responses like so:
98//!
99//! ```rust
100//! let otp_response = "hex:5Bf0 75d9 959d 036f";
101//! let r = rfc2289_otp::parse_otp_response(&otp_response).unwrap();
102//! ```
103//!
104//! If the syntax is valid, you should get an `OTPResponse` as shown below:
105//!
106//! ```rust
107//! type Hex64Bit = [u8; 8];
108//! 
109//! pub enum HexOrWords <'a> {
110//!     Hex(Hex64Bit),
111//!     Words(&'a str),
112//! }
113//!
114//! pub struct OTPInit <'a> {
115//!     pub current_otp: HexOrWords<'a>,
116//!     pub new_otp: HexOrWords<'a>,
117//!     pub new_alg: &'a str,
118//!     pub new_seq_num: usize,
119//!     pub new_seed: &'a str,
120//! }
121//!
122//! pub enum OTPResponse <'a> {
123//!     Init(OTPInit <'a>),
124//!     Current(HexOrWords<'a>)
125//! }
126//! ```
127//!
128//! The server will need to calculate the OTP and compare that value to the decoded
129//! hex or words supplied by the client. You can decode words to the binary OTP
130//! value using `decode_word_format_with_std_dict` like so:
131//!
132//! ```rust
133//! let words = [ "AURA", "ALOE", "HURL", "WING", "BERG", "WAIT" ];
134//! let decoded = rfc2289_otp::decode_word_format_with_std_dict(words).unwrap();
135//! ```
136//!
137//! If the client response is one of the `Init` variants, how the server chooses to
138//! handle this is an implementation detail.
139
140#![no_std]
141use cow_utils::CowUtils;
142use md4::{Md4, Digest};
143use hex::FromHex;
144
145extern crate alloc;
146use alloc::{borrow::ToOwned, boxed::Box};
147
148/// Defined in [IETF RFC 1760](https://www.rfc-editor.org/rfc/rfc1760) for use
149/// in S/KEY, but used OTP in
150/// [IETF RFC 2289](https://www.rfc-editor.org/rfc/rfc2289.html).
151/// 
152/// This is a hard-coded dictionary that maps each word to 11-bits.
153/// 
154/// Used in [convert_to_word_format] and [decode_word_format_with_std_dict].
155#[cfg(feature = "words")]
156pub const STANDARD_DICTIONARY: [&'static str; 2048] = [
157    "A",     "ABE",   "ACE",   "ACT",   "AD",    "ADA",   "ADD",
158    "AGO",   "AID",   "AIM",   "AIR",   "ALL",   "ALP",   "AM",    "AMY",
159    "AN",    "ANA",   "AND",   "ANN",   "ANT",   "ANY",   "APE",   "APS",
160    "APT",   "ARC",   "ARE",   "ARK",   "ARM",   "ART",   "AS",    "ASH",
161    "ASK",   "AT",    "ATE",   "AUG",   "AUK",   "AVE",   "AWE",   "AWK",
162    "AWL",   "AWN",   "AX",    "AYE",   "BAD",   "BAG",   "BAH",   "BAM",
163    "BAN",   "BAR",   "BAT",   "BAY",   "BE",    "BED",   "BEE",   "BEG",
164    "BEN",   "BET",   "BEY",   "BIB",   "BID",   "BIG",   "BIN",   "BIT",
165    "BOB",   "BOG",   "BON",   "BOO",   "BOP",   "BOW",   "BOY",   "BUB",
166    "BUD",   "BUG",   "BUM",   "BUN",   "BUS",   "BUT",   "BUY",   "BY",
167    "BYE",   "CAB",   "CAL",   "CAM",   "CAN",   "CAP",   "CAR",   "CAT",
168    "CAW",   "COD",   "COG",   "COL",   "CON",   "COO",   "COP",   "COT",
169    "COW",   "COY",   "CRY",   "CUB",   "CUE",   "CUP",   "CUR",   "CUT",
170    "DAB",   "DAD",   "DAM",   "DAN",   "DAR",   "DAY",   "DEE",   "DEL",
171    "DEN",   "DES",   "DEW",   "DID",   "DIE",   "DIG",   "DIN",   "DIP",
172    "DO",    "DOE",   "DOG",   "DON",   "DOT",   "DOW",   "DRY",   "DUB",
173    "DUD",   "DUE",   "DUG",   "DUN",   "EAR",   "EAT",   "ED",    "EEL",
174    "EGG",   "EGO",   "ELI",   "ELK",   "ELM",   "ELY",   "EM",    "END",
175    "EST",   "ETC",   "EVA",   "EVE",   "EWE",   "EYE",   "FAD",   "FAN",
176    "FAR",   "FAT",   "FAY",   "FED",   "FEE",   "FEW",   "FIB",   "FIG",
177    "FIN",   "FIR",   "FIT",   "FLO",   "FLY",   "FOE",   "FOG",   "FOR",
178    "FRY",   "FUM",   "FUN",   "FUR",   "GAB",   "GAD",   "GAG",   "GAL",
179    "GAM",   "GAP",   "GAS",   "GAY",   "GEE",   "GEL",   "GEM",   "GET",
180    "GIG",   "GIL",   "GIN",   "GO",    "GOT",   "GUM",   "GUN",   "GUS",
181    "GUT",   "GUY",   "GYM",   "GYP",   "HA",    "HAD",   "HAL",   "HAM",
182    "HAN",   "HAP",   "HAS",   "HAT",   "HAW",   "HAY",   "HE",    "HEM",
183    "HEN",   "HER",   "HEW",   "HEY",   "HI",    "HID",   "HIM",   "HIP",
184    "HIS",   "HIT",   "HO",    "HOB",   "HOC",   "HOE",   "HOG",   "HOP",
185    "HOT",   "HOW",   "HUB",   "HUE",   "HUG",   "HUH",   "HUM",   "HUT",
186    "I",     "ICY",   "IDA",   "IF",    "IKE",   "ILL",   "INK",   "INN",
187    "IO",    "ION",   "IQ",    "IRA",   "IRE",   "IRK",   "IS",    "IT",
188    "ITS",   "IVY",   "JAB",   "JAG",   "JAM",   "JAN",   "JAR",   "JAW",
189    "JAY",   "JET",   "JIG",   "JIM",   "JO",    "JOB",   "JOE",   "JOG",
190    "JOT",   "JOY",   "JUG",   "JUT",   "KAY",   "KEG",   "KEN",   "KEY",
191    "KID",   "KIM",   "KIN",   "KIT",   "LA",    "LAB",   "LAC",   "LAD",
192    "LAG",   "LAM",   "LAP",   "LAW",   "LAY",   "LEA",   "LED",   "LEE",
193    "LEG",   "LEN",   "LEO",   "LET",   "LEW",   "LID",   "LIE",   "LIN",
194    "LIP",   "LIT",   "LO",    "LOB",   "LOG",   "LOP",   "LOS",   "LOT",
195    "LOU",   "LOW",   "LOY",   "LUG",   "LYE",   "MA",    "MAC",   "MAD",
196    "MAE",   "MAN",   "MAO",   "MAP",   "MAT",   "MAW",   "MAY",   "ME",
197    "MEG",   "MEL",   "MEN",   "MET",   "MEW",   "MID",   "MIN",   "MIT",
198    "MOB",   "MOD",   "MOE",   "MOO",   "MOP",   "MOS",   "MOT",   "MOW",
199    "MUD",   "MUG",   "MUM",   "MY",    "NAB",   "NAG",   "NAN",   "NAP",
200    "NAT",   "NAY",   "NE",    "NED",   "NEE",   "NET",   "NEW",   "NIB",
201    "NIL",   "NIP",   "NIT",   "NO",    "NOB",   "NOD",   "NON",   "NOR",
202    "NOT",   "NOV",   "NOW",   "NU",    "NUN",   "NUT",   "O",     "OAF",
203    "OAK",   "OAR",   "OAT",   "ODD",   "ODE",   "OF",    "OFF",   "OFT",
204    "OH",    "OIL",   "OK",    "OLD",   "ON",    "ONE",   "OR",    "ORB",
205    "ORE",   "ORR",   "OS",    "OTT",   "OUR",   "OUT",   "OVA",   "OW",
206    "OWE",   "OWL",   "OWN",   "OX",    "PA",    "PAD",   "PAL",   "PAM",
207    "PAN",   "PAP",   "PAR",   "PAT",   "PAW",   "PAY",   "PEA",   "PEG",
208    "PEN",   "PEP",   "PER",   "PET",   "PEW",   "PHI",   "PI",    "PIE",
209    "PIN",   "PIT",   "PLY",   "PO",    "POD",   "POE",   "POP",   "POT",
210    "POW",   "PRO",   "PRY",   "PUB",   "PUG",   "PUN",   "PUP",   "PUT",
211    "QUO",   "RAG",   "RAM",   "RAN",   "RAP",   "RAT",   "RAW",   "RAY",
212    "REB",   "RED",   "REP",   "RET",   "RIB",   "RID",   "RIG",   "RIM",
213    "RIO",   "RIP",   "ROB",   "ROD",   "ROE",   "RON",   "ROT",   "ROW",
214    "ROY",   "RUB",   "RUE",   "RUG",   "RUM",   "RUN",   "RYE",   "SAC",
215    "SAD",   "SAG",   "SAL",   "SAM",   "SAN",   "SAP",   "SAT",   "SAW",
216    "SAY",   "SEA",   "SEC",   "SEE",   "SEN",   "SET",   "SEW",   "SHE",
217    "SHY",   "SIN",   "SIP",   "SIR",   "SIS",   "SIT",   "SKI",   "SKY",
218    "SLY",   "SO",    "SOB",   "SOD",   "SON",   "SOP",   "SOW",   "SOY",
219    "SPA",   "SPY",   "SUB",   "SUD",   "SUE",   "SUM",   "SUN",   "SUP",
220    "TAB",   "TAD",   "TAG",   "TAN",   "TAP",   "TAR",   "TEA",   "TED",
221    "TEE",   "TEN",   "THE",   "THY",   "TIC",   "TIE",   "TIM",   "TIN",
222    "TIP",   "TO",    "TOE",   "TOG",   "TOM",   "TON",   "TOO",   "TOP",
223    "TOW",   "TOY",   "TRY",   "TUB",   "TUG",   "TUM",   "TUN",   "TWO",
224    "UN",    "UP",    "US",    "USE",   "VAN",   "VAT",   "VET",   "VIE",
225    "WAD",   "WAG",   "WAR",   "WAS",   "WAY",   "WE",    "WEB",   "WED",
226    "WEE",   "WET",   "WHO",   "WHY",   "WIN",   "WIT",   "WOK",   "WON",
227    "WOO",   "WOW",   "WRY",   "WU",    "YAM",   "YAP",   "YAW",   "YE",
228    "YEA",   "YES",   "YET",   "YOU",   "ABED",  "ABEL",  "ABET",  "ABLE",
229    "ABUT",  "ACHE",  "ACID",  "ACME",  "ACRE",  "ACTA",  "ACTS",  "ADAM",
230    "ADDS",  "ADEN",  "AFAR",  "AFRO",  "AGEE",  "AHEM",  "AHOY",  "AIDA",
231    "AIDE",  "AIDS",  "AIRY",  "AJAR",  "AKIN",  "ALAN",  "ALEC",  "ALGA",
232    "ALIA",  "ALLY",  "ALMA",  "ALOE",  "ALSO",  "ALTO",  "ALUM",  "ALVA",
233    "AMEN",  "AMES",  "AMID",  "AMMO",  "AMOK",  "AMOS",  "AMRA",  "ANDY",
234    "ANEW",  "ANNA",  "ANNE",  "ANTE",  "ANTI",  "AQUA",  "ARAB",  "ARCH",
235    "AREA",  "ARGO",  "ARID",  "ARMY",  "ARTS",  "ARTY",  "ASIA",  "ASKS",
236    "ATOM",  "AUNT",  "AURA",  "AUTO",  "AVER",  "AVID",  "AVIS",  "AVON",
237    "AVOW",  "AWAY",  "AWRY",  "BABE",  "BABY",  "BACH",  "BACK",  "BADE",
238    "BAIL",  "BAIT",  "BAKE",  "BALD",  "BALE",  "BALI",  "BALK",  "BALL",
239    "BALM",  "BAND",  "BANE",  "BANG",  "BANK",  "BARB",  "BARD",  "BARE",
240    "BARK",  "BARN",  "BARR",  "BASE",  "BASH",  "BASK",  "BASS",  "BATE",
241    "BATH",  "BAWD",  "BAWL",  "BEAD",  "BEAK",  "BEAM",  "BEAN",  "BEAR",
242    "BEAT",  "BEAU",  "BECK",  "BEEF",  "BEEN",  "BEER",  "BEET",  "BELA",
243    "BELL",  "BELT",  "BEND",  "BENT",  "BERG",  "BERN",  "BERT",  "BESS",
244    "BEST",  "BETA",  "BETH",  "BHOY",  "BIAS",  "BIDE",  "BIEN",  "BILE",
245    "BILK",  "BILL",  "BIND",  "BING",  "BIRD",  "BITE",  "BITS",  "BLAB",
246    "BLAT",  "BLED",  "BLEW",  "BLOB",  "BLOC",  "BLOT",  "BLOW",  "BLUE",
247    "BLUM",  "BLUR",  "BOAR",  "BOAT",  "BOCA",  "BOCK",  "BODE",  "BODY",
248    "BOGY",  "BOHR",  "BOIL",  "BOLD",  "BOLO",  "BOLT",  "BOMB",  "BONA",
249    "BOND",  "BONE",  "BONG",  "BONN",  "BONY",  "BOOK",  "BOOM",  "BOON",
250    "BOOT",  "BORE",  "BORG",  "BORN",  "BOSE",  "BOSS",  "BOTH",  "BOUT",
251    "BOWL",  "BOYD",  "BRAD",  "BRAE",  "BRAG",  "BRAN",  "BRAY",  "BRED",
252    "BREW",  "BRIG",  "BRIM",  "BROW",  "BUCK",  "BUDD",  "BUFF",  "BULB",
253    "BULK",  "BULL",  "BUNK",  "BUNT",  "BUOY",  "BURG",  "BURL",  "BURN",
254    "BURR",  "BURT",  "BURY",  "BUSH",  "BUSS",  "BUST",  "BUSY",  "BYTE",
255    "CADY",  "CAFE",  "CAGE",  "CAIN",  "CAKE",  "CALF",  "CALL",  "CALM",
256    "CAME",  "CANE",  "CANT",  "CARD",  "CARE",  "CARL",  "CARR",  "CART",
257    "CASE",  "CASH",  "CASK",  "CAST",  "CAVE",  "CEIL",  "CELL",  "CENT",
258    "CERN",  "CHAD",  "CHAR",  "CHAT",  "CHAW",  "CHEF",  "CHEN",  "CHEW",
259    "CHIC",  "CHIN",  "CHOU",  "CHOW",  "CHUB",  "CHUG",  "CHUM",  "CITE",
260    "CITY",  "CLAD",  "CLAM",  "CLAN",  "CLAW",  "CLAY",  "CLOD",  "CLOG",
261    "CLOT",  "CLUB",  "CLUE",  "COAL",  "COAT",  "COCA",  "COCK",  "COCO",
262    "CODA",  "CODE",  "CODY",  "COED",  "COIL",  "COIN",  "COKE",  "COLA",
263    "COLD",  "COLT",  "COMA",  "COMB",  "COME",  "COOK",  "COOL",  "COON",
264    "COOT",  "CORD",  "CORE",  "CORK",  "CORN",  "COST",  "COVE",  "COWL",
265    "CRAB",  "CRAG",  "CRAM",  "CRAY",  "CREW",  "CRIB",  "CROW",  "CRUD",
266    "CUBA",  "CUBE",  "CUFF",  "CULL",  "CULT",  "CUNY",  "CURB",  "CURD",
267    "CURE",  "CURL",  "CURT",  "CUTS",  "DADE",  "DALE",  "DAME",  "DANA",
268    "DANE",  "DANG",  "DANK",  "DARE",  "DARK",  "DARN",  "DART",  "DASH",
269    "DATA",  "DATE",  "DAVE",  "DAVY",  "DAWN",  "DAYS",  "DEAD",  "DEAF",
270    "DEAL",  "DEAN",  "DEAR",  "DEBT",  "DECK",  "DEED",  "DEEM",  "DEER",
271    "DEFT",  "DEFY",  "DELL",  "DENT",  "DENY",  "DESK",  "DIAL",  "DICE",
272    "DIED",  "DIET",  "DIME",  "DINE",  "DING",  "DINT",  "DIRE",  "DIRT",
273    "DISC",  "DISH",  "DISK",  "DIVE",  "DOCK",  "DOES",  "DOLE",  "DOLL",
274    "DOLT",  "DOME",  "DONE",  "DOOM",  "DOOR",  "DORA",  "DOSE",  "DOTE",
275    "DOUG",  "DOUR",  "DOVE",  "DOWN",  "DRAB",  "DRAG",  "DRAM",  "DRAW",
276    "DREW",  "DRUB",  "DRUG",  "DRUM",  "DUAL",  "DUCK",  "DUCT",  "DUEL",
277    "DUET",  "DUKE",  "DULL",  "DUMB",  "DUNE",  "DUNK",  "DUSK",  "DUST",
278    "DUTY",  "EACH",  "EARL",  "EARN",  "EASE",  "EAST",  "EASY",  "EBEN",
279    "ECHO",  "EDDY",  "EDEN",  "EDGE",  "EDGY",  "EDIT",  "EDNA",  "EGAN",
280    "ELAN",  "ELBA",  "ELLA",  "ELSE",  "EMIL",  "EMIT",  "EMMA",  "ENDS",
281    "ERIC",  "EROS",  "EVEN",  "EVER",  "EVIL",  "EYED",  "FACE",  "FACT",
282    "FADE",  "FAIL",  "FAIN",  "FAIR",  "FAKE",  "FALL",  "FAME",  "FANG",
283    "FARM",  "FAST",  "FATE",  "FAWN",  "FEAR",  "FEAT",  "FEED",  "FEEL",
284    "FEET",  "FELL",  "FELT",  "FEND",  "FERN",  "FEST",  "FEUD",  "FIEF",
285    "FIGS",  "FILE",  "FILL",  "FILM",  "FIND",  "FINE",  "FINK",  "FIRE",
286    "FIRM",  "FISH",  "FISK",  "FIST",  "FITS",  "FIVE",  "FLAG",  "FLAK",
287    "FLAM",  "FLAT",  "FLAW",  "FLEA",  "FLED",  "FLEW",  "FLIT",  "FLOC",
288    "FLOG",  "FLOW",  "FLUB",  "FLUE",  "FOAL",  "FOAM",  "FOGY",  "FOIL",
289    "FOLD",  "FOLK",  "FOND",  "FONT",  "FOOD",  "FOOL",  "FOOT",  "FORD",
290    "FORE",  "FORK",  "FORM",  "FORT",  "FOSS",  "FOUL",  "FOUR",  "FOWL",
291    "FRAU",  "FRAY",  "FRED",  "FREE",  "FRET",  "FREY",  "FROG",  "FROM",
292    "FUEL",  "FULL",  "FUME",  "FUND",  "FUNK",  "FURY",  "FUSE",  "FUSS",
293    "GAFF",  "GAGE",  "GAIL",  "GAIN",  "GAIT",  "GALA",  "GALE",  "GALL",
294    "GALT",  "GAME",  "GANG",  "GARB",  "GARY",  "GASH",  "GATE",  "GAUL",
295    "GAUR",  "GAVE",  "GAWK",  "GEAR",  "GELD",  "GENE",  "GENT",  "GERM",
296    "GETS",  "GIBE",  "GIFT",  "GILD",  "GILL",  "GILT",  "GINA",  "GIRD",
297    "GIRL",  "GIST",  "GIVE",  "GLAD",  "GLEE",  "GLEN",  "GLIB",  "GLOB",
298    "GLOM",  "GLOW",  "GLUE",  "GLUM",  "GLUT",  "GOAD",  "GOAL",  "GOAT",
299    "GOER",  "GOES",  "GOLD",  "GOLF",  "GONE",  "GONG",  "GOOD",  "GOOF",
300    "GORE",  "GORY",  "GOSH",  "GOUT",  "GOWN",  "GRAB",  "GRAD",  "GRAY",
301    "GREG",  "GREW",  "GREY",  "GRID",  "GRIM",  "GRIN",  "GRIT",  "GROW",
302    "GRUB",  "GULF",  "GULL",  "GUNK",  "GURU",  "GUSH",  "GUST",  "GWEN",
303    "GWYN",  "HAAG",  "HAAS",  "HACK",  "HAIL",  "HAIR",  "HALE",  "HALF",
304    "HALL",  "HALO",  "HALT",  "HAND",  "HANG",  "HANK",  "HANS",  "HARD",
305    "HARK",  "HARM",  "HART",  "HASH",  "HAST",  "HATE",  "HATH",  "HAUL",
306    "HAVE",  "HAWK",  "HAYS",  "HEAD",  "HEAL",  "HEAR",  "HEAT",  "HEBE",
307    "HECK",  "HEED",  "HEEL",  "HEFT",  "HELD",  "HELL",  "HELM",  "HERB",
308    "HERD",  "HERE",  "HERO",  "HERS",  "HESS",  "HEWN",  "HICK",  "HIDE",
309    "HIGH",  "HIKE",  "HILL",  "HILT",  "HIND",  "HINT",  "HIRE",  "HISS",
310    "HIVE",  "HOBO",  "HOCK",  "HOFF",  "HOLD",  "HOLE",  "HOLM",  "HOLT",
311    "HOME",  "HONE",  "HONK",  "HOOD",  "HOOF",  "HOOK",  "HOOT",  "HORN",
312    "HOSE",  "HOST",  "HOUR",  "HOVE",  "HOWE",  "HOWL",  "HOYT",  "HUCK",
313    "HUED",  "HUFF",  "HUGE",  "HUGH",  "HUGO",  "HULK",  "HULL",  "HUNK",
314    "HUNT",  "HURD",  "HURL",  "HURT",  "HUSH",  "HYDE",  "HYMN",  "IBIS",
315    "ICON",  "IDEA",  "IDLE",  "IFFY",  "INCA",  "INCH",  "INTO",  "IONS",
316    "IOTA",  "IOWA",  "IRIS",  "IRMA",  "IRON",  "ISLE",  "ITCH",  "ITEM",
317    "IVAN",  "JACK",  "JADE",  "JAIL",  "JAKE",  "JANE",  "JAVA",  "JEAN",
318    "JEFF",  "JERK",  "JESS",  "JEST",  "JIBE",  "JILL",  "JILT",  "JIVE",
319    "JOAN",  "JOBS",  "JOCK",  "JOEL",  "JOEY",  "JOHN",  "JOIN",  "JOKE",
320    "JOLT",  "JOVE",  "JUDD",  "JUDE",  "JUDO",  "JUDY",  "JUJU",  "JUKE",
321    "JULY",  "JUNE",  "JUNK",  "JUNO",  "JURY",  "JUST",  "JUTE",  "KAHN",
322    "KALE",  "KANE",  "KANT",  "KARL",  "KATE",  "KEEL",  "KEEN",  "KENO",
323    "KENT",  "KERN",  "KERR",  "KEYS",  "KICK",  "KILL",  "KIND",  "KING",
324    "KIRK",  "KISS",  "KITE",  "KLAN",  "KNEE",  "KNEW",  "KNIT",  "KNOB",
325    "KNOT",  "KNOW",  "KOCH",  "KONG",  "KUDO",  "KURD",  "KURT",  "KYLE",
326    "LACE",  "LACK",  "LACY",  "LADY",  "LAID",  "LAIN",  "LAIR",  "LAKE",
327    "LAMB",  "LAME",  "LAND",  "LANE",  "LANG",  "LARD",  "LARK",  "LASS",
328    "LAST",  "LATE",  "LAUD",  "LAVA",  "LAWN",  "LAWS",  "LAYS",  "LEAD",
329    "LEAF",  "LEAK",  "LEAN",  "LEAR",  "LEEK",  "LEER",  "LEFT",  "LEND",
330    "LENS",  "LENT",  "LEON",  "LESK",  "LESS",  "LEST",  "LETS",  "LIAR",
331    "LICE",  "LICK",  "LIED",  "LIEN",  "LIES",  "LIEU",  "LIFE",  "LIFT",
332    "LIKE",  "LILA",  "LILT",  "LILY",  "LIMA",  "LIMB",  "LIME",  "LIND",
333    "LINE",  "LINK",  "LINT",  "LION",  "LISA",  "LIST",  "LIVE",  "LOAD",
334    "LOAF",  "LOAM",  "LOAN",  "LOCK",  "LOFT",  "LOGE",  "LOIS",  "LOLA",
335    "LONE",  "LONG",  "LOOK",  "LOON",  "LOOT",  "LORD",  "LORE",  "LOSE",
336    "LOSS",  "LOST",  "LOUD",  "LOVE",  "LOWE",  "LUCK",  "LUCY",  "LUGE",
337    "LUKE",  "LULU",  "LUND",  "LUNG",  "LURA",  "LURE",  "LURK",  "LUSH",
338    "LUST",  "LYLE",  "LYNN",  "LYON",  "LYRA",  "MACE",  "MADE",  "MAGI",
339    "MAID",  "MAIL",  "MAIN",  "MAKE",  "MALE",  "MALI",  "MALL",  "MALT",
340    "MANA",  "MANN",  "MANY",  "MARC",  "MARE",  "MARK",  "MARS",  "MART",
341    "MARY",  "MASH",  "MASK",  "MASS",  "MAST",  "MATE",  "MATH",  "MAUL",
342    "MAYO",  "MEAD",  "MEAL",  "MEAN",  "MEAT",  "MEEK",  "MEET",  "MELD",
343    "MELT",  "MEMO",  "MEND",  "MENU",  "MERT",  "MESH",  "MESS",  "MICE",
344    "MIKE",  "MILD",  "MILE",  "MILK",  "MILL",  "MILT",  "MIMI",  "MIND",
345    "MINE",  "MINI",  "MINK",  "MINT",  "MIRE",  "MISS",  "MIST",  "MITE",
346    "MITT",  "MOAN",  "MOAT",  "MOCK",  "MODE",  "MOLD",  "MOLE",  "MOLL",
347    "MOLT",  "MONA",  "MONK",  "MONT",  "MOOD",  "MOON",  "MOOR",  "MOOT",
348    "MORE",  "MORN",  "MORT",  "MOSS",  "MOST",  "MOTH",  "MOVE",  "MUCH",
349    "MUCK",  "MUDD",  "MUFF",  "MULE",  "MULL",  "MURK",  "MUSH",  "MUST",
350    "MUTE",  "MUTT",  "MYRA",  "MYTH",  "NAGY",  "NAIL",  "NAIR",  "NAME",
351    "NARY",  "NASH",  "NAVE",  "NAVY",  "NEAL",  "NEAR",  "NEAT",  "NECK",
352    "NEED",  "NEIL",  "NELL",  "NEON",  "NERO",  "NESS",  "NEST",  "NEWS",
353    "NEWT",  "NIBS",  "NICE",  "NICK",  "NILE",  "NINA",  "NINE",  "NOAH",
354    "NODE",  "NOEL",  "NOLL",  "NONE",  "NOOK",  "NOON",  "NORM",  "NOSE",
355    "NOTE",  "NOUN",  "NOVA",  "NUDE",  "NULL",  "NUMB",  "OATH",  "OBEY",
356    "OBOE",  "ODIN",  "OHIO",  "OILY",  "OINT",  "OKAY",  "OLAF",  "OLDY",
357    "OLGA",  "OLIN",  "OMAN",  "OMEN",  "OMIT",  "ONCE",  "ONES",  "ONLY",
358    "ONTO",  "ONUS",  "ORAL",  "ORGY",  "OSLO",  "OTIS",  "OTTO",  "OUCH",
359    "OUST",  "OUTS",  "OVAL",  "OVEN",  "OVER",  "OWLY",  "OWNS",  "QUAD",
360    "QUIT",  "QUOD",  "RACE",  "RACK",  "RACY",  "RAFT",  "RAGE",  "RAID",
361    "RAIL",  "RAIN",  "RAKE",  "RANK",  "RANT",  "RARE",  "RASH",  "RATE",
362    "RAVE",  "RAYS",  "READ",  "REAL",  "REAM",  "REAR",  "RECK",  "REED",
363    "REEF",  "REEK",  "REEL",  "REID",  "REIN",  "RENA",  "REND",  "RENT",
364    "REST",  "RICE",  "RICH",  "RICK",  "RIDE",  "RIFT",  "RILL",  "RIME",
365    "RING",  "RINK",  "RISE",  "RISK",  "RITE",  "ROAD",  "ROAM",  "ROAR",
366    "ROBE",  "ROCK",  "RODE",  "ROIL",  "ROLL",  "ROME",  "ROOD",  "ROOF",
367    "ROOK",  "ROOM",  "ROOT",  "ROSA",  "ROSE",  "ROSS",  "ROSY",  "ROTH",
368    "ROUT",  "ROVE",  "ROWE",  "ROWS",  "RUBE",  "RUBY",  "RUDE",  "RUDY",
369    "RUIN",  "RULE",  "RUNG",  "RUNS",  "RUNT",  "RUSE",  "RUSH",  "RUSK",
370    "RUSS",  "RUST",  "RUTH",  "SACK",  "SAFE",  "SAGE",  "SAID",  "SAIL",
371    "SALE",  "SALK",  "SALT",  "SAME",  "SAND",  "SANE",  "SANG",  "SANK",
372    "SARA",  "SAUL",  "SAVE",  "SAYS",  "SCAN",  "SCAR",  "SCAT",  "SCOT",
373    "SEAL",  "SEAM",  "SEAR",  "SEAT",  "SEED",  "SEEK",  "SEEM",  "SEEN",
374    "SEES",  "SELF",  "SELL",  "SEND",  "SENT",  "SETS",  "SEWN",  "SHAG",
375    "SHAM",  "SHAW",  "SHAY",  "SHED",  "SHIM",  "SHIN",  "SHOD",  "SHOE",
376    "SHOT",  "SHOW",  "SHUN",  "SHUT",  "SICK",  "SIDE",  "SIFT",  "SIGH",
377    "SIGN",  "SILK",  "SILL",  "SILO",  "SILT",  "SINE",  "SING",  "SINK",
378    "SIRE",  "SITE",  "SITS",  "SITU",  "SKAT",  "SKEW",  "SKID",  "SKIM",
379    "SKIN",  "SKIT",  "SLAB",  "SLAM",  "SLAT",  "SLAY",  "SLED",  "SLEW",
380    "SLID",  "SLIM",  "SLIT",  "SLOB",  "SLOG",  "SLOT",  "SLOW",  "SLUG",
381    "SLUM",  "SLUR",  "SMOG",  "SMUG",  "SNAG",  "SNOB",  "SNOW",  "SNUB",
382    "SNUG",  "SOAK",  "SOAR",  "SOCK",  "SODA",  "SOFA",  "SOFT",  "SOIL",
383    "SOLD",  "SOME",  "SONG",  "SOON",  "SOOT",  "SORE",  "SORT",  "SOUL",
384    "SOUR",  "SOWN",  "STAB",  "STAG",  "STAN",  "STAR",  "STAY",  "STEM",
385    "STEW",  "STIR",  "STOW",  "STUB",  "STUN",  "SUCH",  "SUDS",  "SUIT",
386    "SULK",  "SUMS",  "SUNG",  "SUNK",  "SURE",  "SURF",  "SWAB",  "SWAG",
387    "SWAM",  "SWAN",  "SWAT",  "SWAY",  "SWIM",  "SWUM",  "TACK",  "TACT",
388    "TAIL",  "TAKE",  "TALE",  "TALK",  "TALL",  "TANK",  "TASK",  "TATE",
389    "TAUT",  "TEAL",  "TEAM",  "TEAR",  "TECH",  "TEEM",  "TEEN",  "TEET",
390    "TELL",  "TEND",  "TENT",  "TERM",  "TERN",  "TESS",  "TEST",  "THAN",
391    "THAT",  "THEE",  "THEM",  "THEN",  "THEY",  "THIN",  "THIS",  "THUD",
392    "THUG",  "TICK",  "TIDE",  "TIDY",  "TIED",  "TIER",  "TILE",  "TILL",
393    "TILT",  "TIME",  "TINA",  "TINE",  "TINT",  "TINY",  "TIRE",  "TOAD",
394    "TOGO",  "TOIL",  "TOLD",  "TOLL",  "TONE",  "TONG",  "TONY",  "TOOK",
395    "TOOL",  "TOOT",  "TORE",  "TORN",  "TOTE",  "TOUR",  "TOUT",  "TOWN",
396    "TRAG",  "TRAM",  "TRAY",  "TREE",  "TREK",  "TRIG",  "TRIM",  "TRIO",
397    "TROD",  "TROT",  "TROY",  "TRUE",  "TUBA",  "TUBE",  "TUCK",  "TUFT",
398    "TUNA",  "TUNE",  "TUNG",  "TURF",  "TURN",  "TUSK",  "TWIG",  "TWIN",
399    "TWIT",  "ULAN",  "UNIT",  "URGE",  "USED",  "USER",  "USES",  "UTAH",
400    "VAIL",  "VAIN",  "VALE",  "VARY",  "VASE",  "VAST",  "VEAL",  "VEDA",
401    "VEIL",  "VEIN",  "VEND",  "VENT",  "VERB",  "VERY",  "VETO",  "VICE",
402    "VIEW",  "VINE",  "VISE",  "VOID",  "VOLT",  "VOTE",  "WACK",  "WADE",
403    "WAGE",  "WAIL",  "WAIT",  "WAKE",  "WALE",  "WALK",  "WALL",  "WALT",
404    "WAND",  "WANE",  "WANG",  "WANT",  "WARD",  "WARM",  "WARN",  "WART",
405    "WASH",  "WAST",  "WATS",  "WATT",  "WAVE",  "WAVY",  "WAYS",  "WEAK",
406    "WEAL",  "WEAN",  "WEAR",  "WEED",  "WEEK",  "WEIR",  "WELD",  "WELL",
407    "WELT",  "WENT",  "WERE",  "WERT",  "WEST",  "WHAM",  "WHAT",  "WHEE",
408    "WHEN",  "WHET",  "WHOA",  "WHOM",  "WICK",  "WIFE",  "WILD",  "WILL",
409    "WIND",  "WINE",  "WING",  "WINK",  "WINO",  "WIRE",  "WISE",  "WISH",
410    "WITH",  "WOLF",  "WONT",  "WOOD",  "WOOL",  "WORD",  "WORE",  "WORK",
411    "WORM",  "WORN",  "WOVE",  "WRIT",  "WYNN",  "YALE",  "YANG",  "YANK",
412    "YARD",  "YARN",  "YAWL",  "YAWN",  "YEAH",  "YEAR",  "YELL",  "YOGA",
413    "YOKE",
414];
415
416
417/// Folds an arbitrary-length input (greater than 8 bytes) to 8 bytes according
418/// to the algorithm in Appendix A of
419/// [IETF RFC 2289](https://www.rfc-editor.org/rfc/rfc2289.html).
420#[cfg(any(feature = "md4", feature = "md5"))]
421pub fn fold_md (input: &mut [u8]) {
422    let mut j = 0;
423    for i in 8..input.len() {
424        input[j] ^= input[i];
425        j = (j + 1) % 8;
426    }
427}
428
429/// See Appendix A of [IETF RFC 2289](https://www.rfc-editor.org/rfc/rfc2289.html).
430/// 
431/// Note that the example code in this appendix makes reference to some ancient
432/// SHA-1 library: particularly the `SHA_INFO` struct. This struct outputs the
433/// digest into the `digest` field, which is defined as an array of five
434/// `uint32`s. Since the Rust SHA1 library outputs the digest into a 20-byte
435/// array instead, this implementation differs slightly in this regard.
436#[cfg(feature = "sha1")]
437pub fn fold_sha1 (digest: &mut [u8; 20]) {
438    fold_md(&mut digest[..]);
439    digest.swap(0, 3);
440    digest.swap(1, 2);
441    digest.swap(4, 7);
442    digest.swap(5, 6);
443}
444
445const INIT_SIX_WORDS: [&'static str; 6] = [ "A", "A", "A", "A", "A", "A" ];
446
447/// Calculate the checksum, per section 6.0 of
448/// [IETF RFC 2289](https://www.rfc-editor.org/rfc/rfc2289.html).
449pub fn calculate_checksum (input: &[u8; 8]) -> u64 {
450    input
451        .iter()
452        .map(|num| -> u64 {
453            let n = *num;
454            ((n & 0b0000_0011)
455            + ((n & 0b0000_1100) >> 2)
456            + ((n & 0b0011_0000) >> 4)
457            + ((n & 0b1100_0000) >> 6)).into()
458        })
459        .reduce(|acc, curr| acc + curr)
460        .unwrap()
461        & 0b11
462}
463
464/// Encode a 64-bit value using the standard dictionary words defined in
465/// [IETF RFC 1760](https://www.rfc-editor.org/rfc/rfc1760) for use in S/KEY,
466/// and used OTP in
467/// [IETF RFC 2289](https://www.rfc-editor.org/rfc/rfc2289.html).
468/// 
469/// The inverse of [decode_word_format_with_std_dict].
470#[cfg(feature = "words")]
471pub fn convert_to_word_format (result: &[u8; 8]) -> [&'static str; 6] {
472    let checksum: u64 = calculate_checksum(result);
473    let mut result = u64::from_be_bytes(*result);
474    let mut output: [&'static str; 6] = INIT_SIX_WORDS;
475    for i in 0..5 {
476        let bits = (result & (0b11111111111 << (64 - 11))) >> (64 - 11); // 11 bits
477        output[i] = STANDARD_DICTIONARY[bits as usize];
478        result = result.wrapping_shl(11);
479    }
480    let bits: u64 = ((result & (0b11111111111 << (64 - 11))) >> (64 - 11)) + checksum; // 11 bits
481    output[5] = STANDARD_DICTIONARY[bits as usize];
482    output
483}
484
485/// Decode a 64-bit value using the standard dictionary words defined in
486/// [IETF RFC 1760](https://www.rfc-editor.org/rfc/rfc1760) for use in S/KEY,
487/// and used OTP in
488/// [IETF RFC 2289](https://www.rfc-editor.org/rfc/rfc2289.html).
489///
490/// Returns `None` if a word does not appear in the standard dictionary.
491/// Otherwise, returns the decoded bytes and a `bool` indicating whether the
492/// checksum was valid, respectively.
493///
494/// The inverse of [convert_to_word_format].
495#[cfg(feature = "words")]
496pub fn decode_word_format_with_std_dict (words: [&str; 6]) -> Option<([u8; 8], bool)> {
497    let mut output: u64 = 0;
498    for word in words.iter().take(5) {
499        let bits = STANDARD_DICTIONARY.iter().position(|w| *w == *word)?;
500        output <<= 11;
501        output |= bits as u64;
502    }
503    // The last word has special treatment: it's two final bits are a checksum.
504    let bits = STANDARD_DICTIONARY.iter().position(|w| *w == words[5])?;
505    output <<= 9;
506    output |= bits as u64 / 4; // mod by 2^9 just to make sure we don't add checksum bits
507    let checksum_bits = bits as u64 % 4;
508    let output = output.to_be_bytes();
509    let checksum: u64 = calculate_checksum(&output);
510    Some((output, checksum_bits == checksum))
511}
512
513// TODO: Move to documentation
514
515
516/// A parsed OTP challenge string per Section 2.1 of
517/// [IETF RFC 2243](https://www.rfc-editor.org/rfc/rfc2243).
518#[cfg(feature = "parsing")]
519#[derive(Debug)]
520pub struct OTPChallenge <'a> {
521    pub hash_alg: &'a str,
522    pub hash_count: usize,
523    pub seed: &'a str,
524}
525
526/// A parsed OTP init string per Section 4.1 of
527/// [IETF RFC 2243](https://www.rfc-editor.org/rfc/rfc2243).
528#[cfg(feature = "parsing")]
529pub fn parse_otp_challenge <'a> (s: &'a str) -> Option<OTPChallenge<'a>> {
530    if s.len() < 9 { // This is the smallest that an OTP challenge can be.
531        return None;
532    }
533    if s.len() > 128 {
534        return None; // To prevent denial of service via outrageous values.
535    }
536    if !s.starts_with("otp-") {
537        return None;
538    }
539    let x = &s[4..];
540    let mut hash_alg: Option<&'a str> = None;
541    let mut seed: Option<&'a str> = None;
542    let mut count: Option<usize> = None;
543    for token in x.split_ascii_whitespace() {
544        if hash_alg.is_none() {
545            hash_alg = Some(token);
546        }
547        else if count.is_none() {
548            count = Some(usize::from_str_radix(token, 10).ok()?);
549        }
550        else if seed.is_none() {
551            seed = Some(token);
552            break;
553        }
554    }
555    Some(OTPChallenge{
556        hash_alg: hash_alg?,
557        seed: seed?,
558        hash_count: count?,
559    })
560}
561
562pub type Hex64Bit = [u8; 8];
563
564/// A Hex value or dictionary words
565#[cfg(feature = "parsing")]
566#[derive(Debug, PartialEq, Eq)]
567pub enum HexOrWords <'a> {
568    Hex(Hex64Bit),
569    Words(&'a str),
570}
571
572impl HexOrWords<'_> {
573
574    pub fn try_into_bytes (&self) -> Option<[u8; 8]> {
575        match self {
576            HexOrWords::Hex(h) => Some(h.to_owned()),
577            HexOrWords::Words(w) => {
578                let mut w = w.split_ascii_whitespace();
579                let six_words = [ w.next(), w.next(), w.next(), w.next(), w.next(), w.next() ];
580                if six_words[5].is_none() {
581                    return None;
582                }
583                if w.next().is_some() {
584                    return None;
585                }
586                let six_words = [
587                    six_words[0].unwrap(),
588                    six_words[1].unwrap(),
589                    six_words[2].unwrap(),
590                    six_words[3].unwrap(),
591                    six_words[4].unwrap(),
592                    six_words[5].unwrap(),
593                ];
594                let maybe_64bits = decode_word_format_with_std_dict(six_words);
595                if maybe_64bits.is_none() {
596                    return None;
597                }
598                let (v, valid_checksum) = maybe_64bits.unwrap();
599                if !valid_checksum {
600                    return None;
601                }
602                Some(v)
603            },
604        }
605    }
606
607}
608
609/// A parsed OTP init string per Section 4.1 of
610/// [IETF RFC 2243](https://www.rfc-editor.org/rfc/rfc2243).
611#[cfg(feature = "parsing")]
612#[derive(Debug)]
613pub struct OTPInit <'a> {
614    pub current_otp: HexOrWords<'a>,
615    pub new_otp: HexOrWords<'a>,
616    pub new_alg: &'a str,
617    pub new_seq_num: usize,
618    pub new_seed: &'a str,
619}
620
621/// A parsed OTP response per Sections 3 and 4 of
622/// [IETF RFC 2243](https://www.rfc-editor.org/rfc/rfc2243).
623#[cfg(feature = "parsing")]
624#[derive(Debug)]
625pub enum OTPResponse <'a> {
626    Init(OTPInit <'a>),
627    Current(HexOrWords<'a>)
628}
629
630/// Parse OTP `init-hex-response` per Section 4.1 of
631/// [IETF RFC 2243](https://www.rfc-editor.org/rfc/rfc2243).
632#[cfg(feature = "parsing")]
633fn parse_otp_init_hex <'a> (s: &'a str) -> Option<OTPInit<'a>> {
634    let mut sections = s.split(":");
635    let current_otp = sections.next()?.cow_replace(" ", "");
636    let new_params = sections.next()?;
637    let new_otp = sections.next()?.cow_replace(" ", "");
638    if sections.next().is_some() {
639        return None;
640    }
641    let current_otp = <Hex64Bit>::from_hex(current_otp.cow_replace("\t", "").as_ref()).ok()?;
642    let new_otp = <Hex64Bit>::from_hex(new_otp.cow_replace("\t", "").as_ref()).ok()?;
643    let mut params = new_params.split(" ");
644    let algorithm = params.next()?;
645    let sequence_number = params.next()?;
646    let seed = params.next()?;
647    let sequence_number = <usize>::from_str_radix(sequence_number, 10).ok()?;
648    Some(OTPInit {
649        current_otp: HexOrWords::Hex(current_otp),
650        new_otp: HexOrWords::Hex(new_otp),
651        new_alg: algorithm,
652        new_seq_num: sequence_number,
653        new_seed: seed,
654    })
655}
656
657/// Parse OTP `init-word-response` per Section 4.1 of
658/// [IETF RFC 2243](https://www.rfc-editor.org/rfc/rfc2243).
659#[cfg(feature = "parsing")]
660fn parse_otp_init_word <'a> (s: &'a str) -> Option<OTPInit<'a>> {
661    let mut sections = s.split(":");
662    let current_otp = sections.next()?;
663    let new_params = sections.next()?;
664    let new_otp = sections.next()?;
665    if sections.next().is_some() {
666        return None;
667    }
668    let mut params = new_params.split(" ");
669    let algorithm = params.next()?;
670    let sequence_number = params.next()?;
671    let seed = params.next()?;
672    let sequence_number = <usize>::from_str_radix(sequence_number, 10).ok()?;
673    Some(OTPInit {
674        current_otp: HexOrWords::Words(current_otp),
675        new_otp: HexOrWords::Words(new_otp),
676        new_alg: algorithm,
677        new_seq_num: sequence_number,
678        new_seed: seed,
679    })
680}
681
682/// Parse OTP init strings per Section 4.1 of
683/// [IETF RFC 2243](https://www.rfc-editor.org/rfc/rfc2243).
684#[cfg(feature = "parsing")]
685pub fn parse_otp_init <'a> (s: &'a str) -> Option<OTPInit<'a>> {
686    if s.len() <= 50 || s.len() > 100 { // Arbitrary upper limit
687        return None;
688    }
689    if s.starts_with("init-hex:") {
690        parse_otp_init_hex(&s[9..])
691    }
692    else if s.starts_with("init-word:") {
693        parse_otp_init_word(&s[10..])
694    }
695    else {
696        None
697    }
698}
699
700/// Parse OTP response strings per Sections 3 and 4 of
701/// [IETF RFC 2243](https://www.rfc-editor.org/rfc/rfc2243).
702#[cfg(feature = "parsing")]
703pub fn parse_otp_response <'a> (s: &'a str) -> Option<OTPResponse<'a>> {
704    if s.len() < 20 || s.len() > 100 { // Arbitrary upper limit
705        return None;
706    }
707    if s.starts_with("hex:") {
708        let h = <Hex64Bit>::from_hex(&s[4..].cow_replace(" ", "").cow_replace("\t", "").as_ref()).ok()?;
709        Some(OTPResponse::Current(HexOrWords::Hex(h)))
710    }
711    else if s.starts_with("word:") {
712        Some(OTPResponse::Current(HexOrWords::Words(&s[5..])))
713    }
714    else if s.starts_with("init-hex:") {
715        parse_otp_init_hex(&s[9..]).map(|i| OTPResponse::Init(i))
716    }
717    else if s.starts_with("init-word:") {
718        parse_otp_init_word(&s[10..]).map(|i| OTPResponse::Init(i))
719    }
720    else {
721        None
722    }
723}
724
725/// Calculates the One-Time Pad using an arbitrary dynamic digest object
726#[cfg(feature = "dyndig")]
727fn calculate_otp_via_digest (
728    hasher: &mut dyn digest::DynDigest,
729    passphrase: &str,
730    seed: &str,
731    count: usize,
732) -> [u8; 8] {
733    hasher.update(seed.as_bytes());
734    hasher.update(passphrase.as_bytes());
735    let mut digest_bytes: [u8; 64] = [0; 64]; // Will accommodate a theoretical SHA-1024.
736    hasher.finalize_into_reset(digest_bytes.as_mut_slice()).expect("hash size too large");
737    fold_md(&mut digest_bytes);
738    let mut prev_hash = digest_bytes;
739    for _ in 0..count {
740        hasher.update(&prev_hash[0..hasher.output_size()]);
741        hasher.finalize_into_reset(digest_bytes.as_mut_slice()).expect("hash size too large");
742        fold_md(&mut digest_bytes[0..hasher.output_size()]);
743        prev_hash = digest_bytes;
744    }
745    [
746        prev_hash[0],
747        prev_hash[1],
748        prev_hash[2],
749        prev_hash[3],
750        prev_hash[4],
751        prev_hash[5],
752        prev_hash[6],
753        prev_hash[7],
754    ]
755}
756
757/// Calculates the One-Time Pad using the `md4` algorithm.
758#[cfg(feature = "md4")]
759pub fn calculate_md4_otp (
760    passphrase: &str,
761    lowercased_seed: &str,
762    count: usize,
763) -> Option<[u8; 8]> {
764    let mut m = Md4::new();
765    m.update(lowercased_seed.as_bytes());
766    m.update(passphrase.as_bytes());
767    let mut digest_bytes = m.finalize();
768    fold_md(&mut digest_bytes);
769    let mut prev_hash = digest_bytes;
770    for _ in 0..count {
771        let mut m = Md4::new();
772        m.update(&prev_hash[0..8]);
773        let mut digest_bytes = m.finalize();
774        fold_md(&mut digest_bytes);
775        prev_hash = digest_bytes;
776    }
777    Some([
778        prev_hash[0],
779        prev_hash[1],
780        prev_hash[2],
781        prev_hash[3],
782        prev_hash[4],
783        prev_hash[5],
784        prev_hash[6],
785        prev_hash[7],
786    ])
787}
788
789/// Calculates the One-Time Pad using the `md5` algorithm.
790#[cfg(feature = "md5")]
791pub fn calculate_md5_otp (
792    passphrase: &str,
793    lowercased_seed: &str,
794    count: usize,
795) -> Option<[u8; 8]> {
796    let digest = md5::compute([
797        lowercased_seed.as_ref(),
798        passphrase,
799    ].concat());
800    let mut digest_bytes = digest.0;
801    fold_md(&mut digest_bytes);
802    let mut prev_hash = digest_bytes;
803    for _ in 0..count {
804        let mut digest_bytes = md5::compute(&prev_hash[0..8]).0;
805        fold_md(&mut digest_bytes);
806        prev_hash = digest_bytes;
807    }
808    Some([
809        prev_hash[0],
810        prev_hash[1],
811        prev_hash[2],
812        prev_hash[3],
813        prev_hash[4],
814        prev_hash[5],
815        prev_hash[6],
816        prev_hash[7],
817    ])
818}
819
820/// Calculates the One-Time Pad using the `sha1` algorithm.
821#[cfg(feature = "sha1")]
822pub fn calculate_sha1_otp (
823    passphrase: &str,
824    lowercased_seed: &str,
825    count: usize,
826) -> Option<[u8; 8]> {
827    let mut m = sha1_smol::Sha1::new();
828    m.update(lowercased_seed.as_bytes());
829    m.update(passphrase.as_bytes());
830    let mut digest_bytes = m.digest().bytes();
831    fold_sha1(&mut digest_bytes);
832    let mut prev_hash = digest_bytes;
833    for _ in 0..count {
834        let mut m = sha1_smol::Sha1::new();
835        m.update(&prev_hash[0..8]);
836        let mut digest_bytes = m.digest().bytes();
837        fold_sha1(&mut digest_bytes);
838        prev_hash = digest_bytes;
839    }
840    Some([
841        prev_hash[0],
842        prev_hash[1],
843        prev_hash[2],
844        prev_hash[3],
845        prev_hash[4],
846        prev_hash[5],
847        prev_hash[6],
848        prev_hash[7],
849    ])
850}
851
852/// Calculate an OTP value from supplied parameters, per Section 6.0 of
853/// [IETF RFC 2289](https://www.rfc-editor.org/rfc/rfc2289.html).
854///
855/// Returns `None` if the algorithm is not understood.
856/// 
857/// The `maybe_get_digest` function is a function that takes a digest name and
858/// returns a corresponding `DynDigest`. This is so the types of hash algorithms
859/// supported can be extended. This argument is only present if the `dyndig`
860/// feature flag is enabled.
861pub fn calculate_otp (
862    hash_alg: &str,
863    passphrase: &str,
864    seed: &str,
865    count: usize,
866    #[cfg(feature = "dyndig")]
867    maybe_get_digest: Option<fn(&str) -> Option<Box<dyn digest::DynDigest>>>,
868) -> Option<[u8; 8]> {
869    let lowercased_seed = seed.cow_to_ascii_lowercase();
870    match hash_alg {
871        #[cfg(feature = "md4")]
872        "md4" => calculate_md4_otp(passphrase, lowercased_seed.as_ref(), count),
873        #[cfg(feature = "md5")]
874        "md5" => calculate_md5_otp(passphrase, lowercased_seed.as_ref(), count),
875        #[cfg(feature = "sha1")]
876        "sha1" => calculate_sha1_otp(passphrase, lowercased_seed.as_ref(), count),
877        #[cfg(feature = "dyndig")]
878        _ => {
879            let get_digest = maybe_get_digest?;
880            let mut digest = get_digest(hash_alg)?;
881            Some(calculate_otp_via_digest(digest.as_mut(), passphrase, lowercased_seed.as_ref(), count))
882        },
883        #[cfg(not(feature = "dyndig"))]
884        _ => {
885            None
886        }
887    }
888}
889
890#[cfg(test)]
891mod tests {
892    use super::*;
893
894    // TODO: Test folding
895
896    type TestCase = (&'static str, &'static str, usize, [u8; 8], &'static str);
897
898    const OFFICIAL_MD4_TEST_CASES: [TestCase; 9] = [
899        ("This is a test.", "TeSt",      0, [ 0xD1, 0x85, 0x42, 0x18, 0xEB, 0xBB, 0x0B, 0x51 ], "ROME MUG FRED SCAN LIVE LACE"),
900        ("This is a test.", "TeSt",      1, [ 0x63, 0x47, 0x3E, 0xF0, 0x1C, 0xD0, 0xB4, 0x44 ], "CARD SAD MINI RYE COL KIN"),
901        ("This is a test.", "TeSt",     99, [ 0xC5, 0xE6, 0x12, 0x77, 0x6E, 0x6C, 0x23, 0x7A ], "NOTE OUT IBIS SINK NAVE MODE"),
902        ("AbCdEfGhIjK", "alpha1",        0, [ 0x50, 0x07, 0x6F, 0x47, 0xEB, 0x1A, 0xDE, 0x4E ], "AWAY SEN ROOK SALT LICE MAP"),
903        ("AbCdEfGhIjK", "alpha1",        1, [ 0x65, 0xD2, 0x0D, 0x19, 0x49, 0xB5, 0xF7, 0xAB ], "CHEW GRIM WU HANG BUCK SAID"),
904        ("AbCdEfGhIjK", "alpha1",       99, [ 0xD1, 0x50, 0xC8, 0x2C, 0xCE, 0x6F, 0x62, 0xD1 ], "ROIL FREE COG HUNK WAIT COCA"),
905        ("OTP's are good", "correct",    0, [ 0x84, 0x9C, 0x79, 0xD4, 0xF6, 0xF5, 0x53, 0x88 ], "FOOL STEM DONE TOOL BECK NILE"),
906        ("OTP's are good", "correct",    1, [ 0x8C, 0x09, 0x92, 0xFB, 0x25, 0x08, 0x47, 0xB1 ], "GIST AMOS MOOT AIDS FOOD SEEM"),
907        ("OTP's are good", "correct",   99, [ 0x3F, 0x3B, 0xF4, 0xB4, 0x14, 0x5F, 0xD7, 0x4B ], "TAG SLOW NOV MIN WOOL KENO"),
908    ];
909
910    const OFFICIAL_MD5_TEST_CASES: [TestCase; 9] = [
911        ("This is a test.", "TeSt",      0, [ 0x9E, 0x87, 0x61, 0x34, 0xD9, 0x04, 0x99, 0xDD ], "INCH SEA ANNE LONG AHEM TOUR"),
912        ("This is a test.", "TeSt",      1, [ 0x79, 0x65, 0xE0, 0x54, 0x36, 0xF5, 0x02, 0x9F ], "EASE OIL FUM CURE AWRY AVIS"),
913        ("This is a test.", "TeSt",     99, [ 0x50, 0xFE, 0x19, 0x62, 0xC4, 0x96, 0x58, 0x80 ], "BAIL TUFT BITS GANG CHEF THY"),
914        ("AbCdEfGhIjK", "alpha1",        0, [ 0x87, 0x06, 0x6D, 0xD9, 0x64, 0x4B, 0xF2, 0x06 ], "FULL PEW DOWN ONCE MORT ARC"),
915        ("AbCdEfGhIjK", "alpha1",        1, [ 0x7C, 0xD3, 0x4C, 0x10, 0x40, 0xAD, 0xD1, 0x4B ], "FACT HOOF AT FIST SITE KENT"),
916        ("AbCdEfGhIjK", "alpha1",       99, [ 0x5A, 0xA3, 0x7A, 0x81, 0xF2, 0x12, 0x14, 0x6C ], "BODE HOP JAKE STOW JUT RAP"),
917        ("OTP's are good", "correct",    0, [ 0xF2, 0x05, 0x75, 0x39, 0x43, 0xDE, 0x4C, 0xF9 ], "ULAN NEW ARMY FUSE SUIT EYED"),
918        ("OTP's are good", "correct",    1, [ 0xDD, 0xCD, 0xAC, 0x95, 0x6F, 0x23, 0x49, 0x37 ], "SKIM CULT LOB SLAM POE HOWL"),
919        ("OTP's are good", "correct",   99, [ 0xB2, 0x03, 0xE2, 0x8F, 0xA5, 0x25, 0xBE, 0x47 ], "LONG IVY JULY AJAR BOND LEE"),
920    ];
921
922    const OFFICIAL_SHA1_TEST_CASES: [TestCase; 9] = [
923        ("This is a test.", "TeSt",      0, [ 0xBB, 0x9E, 0x6A, 0xE1, 0x97, 0x9D, 0x8F, 0xF4 ], "MILT VARY MAST OK SEES WENT"),
924        ("This is a test.", "TeSt",      1, [ 0x63, 0xD9, 0x36, 0x63, 0x97, 0x34, 0x38, 0x5B ], "CART OTTO HIVE ODE VAT NUT"),
925        ("This is a test.", "TeSt",     99, [ 0x87, 0xFE, 0xC7, 0x76, 0x8B, 0x73, 0xCC, 0xF9 ], "GAFF WAIT SKID GIG SKY EYED"),
926        ("AbCdEfGhIjK", "alpha1",        0, [ 0xAD, 0x85, 0xF6, 0x58, 0xEB, 0xE3, 0x83, 0xC9 ], "LEST OR HEEL SCOT ROB SUIT"),
927        ("AbCdEfGhIjK", "alpha1",        1, [ 0xD0, 0x7C, 0xE2, 0x29, 0xB5, 0xCF, 0x11, 0x9B ], "RITE TAKE GELD COST TUNE RECK"),
928        ("AbCdEfGhIjK", "alpha1",       99, [ 0x27, 0xBC, 0x71, 0x03, 0x5A, 0xAF, 0x3D, 0xC6 ], "MAY STAR TIN LYON VEDA STAN"),
929        ("OTP's are good", "correct",    0, [ 0xD5, 0x1F, 0x3E, 0x99, 0xBF, 0x8E, 0x6F, 0x0B ], "RUST WELT KICK FELL TAIL FRAU"),
930        ("OTP's are good", "correct",    1, [ 0x82, 0xAE, 0xB5, 0x2D, 0x94, 0x37, 0x74, 0xE4 ], "FLIT DOSE ALSO MEW DRUM DEFY"),
931        ("OTP's are good", "correct",   99, [ 0x4F, 0x29, 0x6A, 0x74, 0xFE, 0x15, 0x67, 0xEC ], "AURA ALOE HURL WING BERG WAIT"),
932    ];
933
934    #[test]
935    #[cfg(all(feature = "md4", feature = "words"))]
936    fn passes_official_md4_test_cases() {
937        for test_case in OFFICIAL_MD4_TEST_CASES {
938            let otp = calculate_otp("md4", test_case.0, test_case.1, test_case.2, None).unwrap();
939            assert_eq!(otp, test_case.3);
940            let words = convert_to_word_format(&otp);
941            assert_eq!(words.join(" "), test_case.4);
942        }
943    }
944
945    #[test]
946    #[cfg(all(feature = "md5", feature = "words"))]
947    fn passes_official_md5_test_cases() {
948        for test_case in OFFICIAL_MD5_TEST_CASES {
949            let otp = calculate_otp("md5", test_case.0, test_case.1, test_case.2, None).unwrap();
950            assert_eq!(otp, test_case.3);
951            let words = convert_to_word_format(&otp);
952            assert_eq!(words.join(" "), test_case.4);
953        }
954    }
955
956    #[test]
957    #[cfg(all(feature = "sha1", feature = "words"))]
958    fn passes_official_sha1_test_cases() {
959        for test_case in OFFICIAL_SHA1_TEST_CASES {
960            let otp = calculate_otp("sha1", test_case.0, test_case.1, test_case.2, None).unwrap();
961            assert_eq!(otp, test_case.3);
962            let words = convert_to_word_format(&otp);
963            assert_eq!(words.join(" "), test_case.4);
964            let decoded = decode_word_format_with_std_dict(words).unwrap();
965            assert_eq!(decoded, (test_case.3, true));
966        }
967    }
968
969    #[test]
970    #[cfg(feature = "parsing")]
971    fn parses_otp_challenge() {
972        let challenge = "otp-md5 487 dog2";
973        let challenge = parse_otp_challenge(challenge).unwrap();
974        assert_eq!(challenge.hash_alg, "md5");
975        assert_eq!(challenge.hash_count, 487);
976        assert_eq!(challenge.seed, "dog2");
977    }
978
979    #[test]
980    #[cfg(feature = "parsing")]
981    fn parses_otp_response_hex () {
982        let otp_response = "hex:5Bf0 75d9 959d 036f";
983        let r = parse_otp_response(&otp_response).unwrap();
984        if let OTPResponse::Current(x) = r {
985            if let HexOrWords::Hex(h) = x {
986                assert_eq!(h, [ 0x5B, 0xf0, 0x75, 0xd9, 0x95, 0x9d, 0x03, 0x6f ]);
987            } else {
988                panic!()
989            }
990        } else {
991            panic!()
992        }
993    }
994
995    #[test]
996    #[cfg(feature = "parsing")]
997    fn parses_otp_response_word () {
998        let otp_response = "word:BOND FOGY DRAB NE RISE MART";
999        let r = parse_otp_response(&otp_response).unwrap();
1000        if let OTPResponse::Current(x) = r {
1001            if let HexOrWords::Words(w) = x {
1002                assert_eq!(w, "BOND FOGY DRAB NE RISE MART");
1003            } else {
1004                panic!()
1005            }
1006        } else {
1007            panic!()
1008        }
1009    }
1010
1011    #[test]
1012    #[cfg(feature = "parsing")]
1013    fn parses_otp_response_init_hex () {
1014        let otp_response = "init-hex:5bf0 75d9 959d 036f:md5 499 ke1235:3712 dcb4 aa53 16c1";
1015        let r = parse_otp_response(&otp_response).unwrap();
1016        if let OTPResponse::Init(x) = r {
1017            assert_eq!(x.current_otp, HexOrWords::Hex([ 0x5B, 0xf0, 0x75, 0xd9, 0x95, 0x9d, 0x03, 0x6f ]));
1018            assert_eq!(x.new_otp, HexOrWords::Hex([ 0x37, 0x12, 0xdc, 0xb4, 0xaa, 0x53, 0x16, 0xc1 ]));
1019            assert_eq!(x.new_alg, "md5");
1020            assert_eq!(x.new_seq_num, 499);
1021            assert_eq!(x.new_seed, "ke1235");
1022        } else {
1023            panic!()
1024        }
1025    }
1026
1027    #[test]
1028    #[cfg(feature = "parsing")]
1029    fn parses_otp_response_init_word () {
1030        let otp_response = "init-word:BOND FOGY DRAB NE RISE MART:md5 499 ke1235:RED HERD NOW BEAN PA BURG";
1031        let r = parse_otp_response(&otp_response).unwrap();
1032        if let OTPResponse::Init(x) = r {
1033            assert_eq!(x.current_otp, HexOrWords::Words("BOND FOGY DRAB NE RISE MART"));
1034            assert_eq!(x.new_otp, HexOrWords::Words("RED HERD NOW BEAN PA BURG"));
1035            assert_eq!(x.new_alg, "md5");
1036            assert_eq!(x.new_seq_num, 499);
1037            assert_eq!(x.new_seed, "ke1235");
1038        } else {
1039            panic!()
1040        }
1041    }
1042}