Skip to content

This project demonstrates the use of Rust to develop a multiplatform Time-based One-Time Password (TOTP) module. It highlights Rust's high performance, memory safety, and interoperability with other languages for server-side, mobile, and web applications.

Notifications You must be signed in to change notification settings

fdimarh/rust-totp-multiplatform-module

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Revolutionizing TOTP Login: A Multiplatform Module Implementation using Rust (Android iOS Server and Web Apps)

_cbbf5e4f-f309-4694-93ed-31bf896aff2e (1).jpg

Introduction

With the rise of multiplatform applications, it's increasingly important to write code that is portable across a variety of systems. One programming language that excels at this is Rust, which is gaining traction for its memory-safety guarantees and its performance close to that of C++. In this blog post, we will delve into the process of creating a multiplatform module using Rust, focusing on cross-compiling, conditional compilation, and platform-specific APIs.

One of the key architectural principles that can be applied in Rust is the Clean Architecture, also known as the Union Architecture. This architectural style emphasizes separation of concerns and the independence of business rules from the external factors like UI, database, web servers, etc. In this context, Rust serves as the core of business logic. The business logic written in Rust is isolated from the rest of the system, meaning it doesn't know anything about the outside world. This makes the code easier to test, maintain, and understand. It also allows the business logic to be re-used across different platforms, which is especially important in a multiplatform module. By adhering to the principles of Clean Architecture, you can write code in Rust that stands the test of time, is easy to refactor and adapt, and can be ported to multiple platforms with minimal effort.

In the next section, we will explore how to implement the Time-based One-Time Password (TOTP) algorithm using Rust and how to integrate it in a multiplatform module. We will also discuss how to make our Rust code interact with Java for server-side and mobile applications respectively using swift and kotlin.

TOTP in a Nutshell

Untitled.png

credit to velog.io

The Time-based One-Time Password (TOTP) algorithm is an ingenious method devised for the generation of unique, temporary passwords possessing a validity that spans only a short duration of time. This particular algorithm has found widespread application in many digital platforms, where it is predominantly utilized for the process of two-factor authentication (2FA), thereby adding an extra layer of security to protect user data.

The fundamental functioning of the TOTP algorithm involves the combination of a shared secret key with the current timestamp in a systematic manner. This combination is then subjected to a hashing process to generate a unique, one-time password. It's important to note that this password is singular in nature, meaning it can't be used more than once, thereby significantly reducing the risk of unauthorized access.

On the other end, the server performs the same calculation in order to verify the authenticity of the password. Given that both the server and the user's device are privy to the secret key and the current time, they can independently calculate and arrive at the same password. This synchronization between the server and the user's device provides an additional layer of security. In the event that the password is intercepted or falls into the wrong hands, it becomes virtually useless after its single use, thereby safeguarding the user's account from potential security breaches.

Engineering the TOTP Module and Implementation

Untitled (1).png

In engineering the TOTP module using Rust, we created a shared totp as business domain function which holds the secret key and a function for generating the one-time password. This struct is utilized by both the server-side and mobile applications to generate the TOTP. For the server-side application, we implemented a function to validate the TOTP received from the user by recreating the password using the secret key and the current time, and comparing it to the received TOTP. If they match, the user is authenticated.

For the mobile application, we implemented a function to generate and display the TOTP to the user. We also introduced a functionality to synchronize the time with the server to ensure that the generated TOTP is always valid. The Rust code for the TOTP module was then cross-compiled to be compatible with the server-side application and the mobile applications written in Swift and Kotlin. This process showcases the power of Rust in writing reusable, platform-independent code, and its capability in interfacing with other languages.

In our case, we made use Rust server-side. For the mobile applications, we used the FFI (Foreign Function Interface) to enable Swift and Kotlin to interface with the Rust code. With these tools, we were able to seamlessly integrate our TOTP module across different platforms.

Implementation Process

The implementation process of our multiplatform TOTP module involved several steps. First, we started with the creation of the core business logic in Rust. This included the totp struct which holds the secret key and provides a method for generating one-time passwords.

Next, we proceeded to create a Rust Foreign Function Interface (FFI) for Android mobile applications. This FFI allowed our Kotlin application to interact with the Rust code, providing a seamless integration between the two.

The third step involved creating a Rust FFI for Swift, resulting in an xcframework. This enabled our Swift-based iOS application to utilize the Rust TOTP module, again demonstrating the interoperability of Rust with other programming languages.

Finally, we implemented the web application. This involved integrating the Rust TOTP module with our server-side application to enable TOTP generation and validation. This comprehensive implementation process showcased the power of Rust in creating reusable, platform-independent code that can be interfaced with multiple programming languages.

Please note, while this guide focuses on the process of delivering a TOTP module using Rust across multiple platforms, it will not cover basic configuration aspects such as Rust installation, the creation of makefiles, running Android and iOS project. This is to keep the focus on illustrating the core concepts and steps involved in the development and integration process. For guidance on these preliminary setup steps, we recommend referring to the official Rust documentation or other dedicated resources.

1. TOTP Generator Rust Business Logic

To begin with, we defined a totp public module in Rust that holds the shared secret key. It includes a method get_pin_number() that combines the shared secret key with the current timestamp, hashes the result, and returns a unique, one-time password. This piece of code forms the core of our TOTP generator and is designed to be platform-independent, which is integral for a multiplatform module.

The get_pin_number() Function

The core function in our TOTP Rust module is get_pin_number(). This function takes four parameters: otp_digit, time_threshold, time_expiry, and secret.

otp_digit is an integer that specifies the number of digits in the OTP. The length of the OTP can significantly impact its security. A longer OTP is generally more secure, but it may also be more difficult for users to remember and input correctly.

time_threshold is the time window in which the OTP is valid. This is usually a short period of time, such as 1 second until 3 seconds. Shorter time windows increase security by reducing the window of opportunity for an attacker to use a stolen OTP.

time_expiry is the timestamp at which the OTP expires. After this time, the OTP cannot be used to authenticate the user. Shorter time windows increase security by reducing the window of opportunity for an attacker to use a stolen OTP. Best practices for this expiry is 30 seconds

secret is the shared secret key that is used to generate the OTP. This secret key is known only to the user and the server, and it is used to ensure that the OTPs generated by the user and the server match.

The get_pin_number() function combines the shared secret key with the current timestamp, hashes the result, and returns a unique, one-time password. The OTP is generated using the following steps:

  1. The current timestamp is divided by the time window to get a time counter value.
  2. The time counter value is combined with the shared secret key to form a data string.
  3. The data string is hashed using a cryptographic hash function to produce a hash value.
  4. A portion of the hash value is selected to form the OTP. The length of this portion is determined by the otp_digit parameter.
  5. The OTP is returned as the result of the function.

By using this method, we can ensure that each OTP is unique and can only be used once, providing robust security for user authentication.

pub mod totp {
    use totp_rs::{Algorithm, TOTP, Secret};
    
    pub fn get_pin_number(otp_digit: usize, time_threshold: u8, time_expiry: u64, secret: String) -> String {
        let totp = TOTP::new(
            Algorithm::SHA1,
            otp_digit,
            time_threshold,
            time_expiry,
            Secret::Raw(secret.as_bytes().to_vec()).to_bytes().unwrap(),
        ).unwrap();
        let token = totp.generate_current().unwrap();
        return token;
    }
}

To ensure the proper functioning of the get_pin_number() function in our TOTP Rust module, we implemented unit testing. These tests were designed to validate the accuracy of the OTP generated from a given secret key and timestamp. Several test cases were created, each with a different set of inputs for otp_digit, time_threshold, time_expiry, and secret. For each test case, the output of the function was compared with the expected OTP value. Successful matches across all test cases indicated that the get_pin_number() function was working as intended, providing reliable and accurate OTP generation.

#[cfg(test)]
mod tests {
    use crate::totp::get_pin_number;

    #[test]
    fn otp_works() {
        let result = get_pin_number(6, 3, 30, "12345678901234567890".to_string());
        print!("OTP {}",result);
        assert_eq!(result.len(), 6);
    }
}

2. Android TOTP Rust Module

In the Android TOTP Rust Module, we first created a Foreign Function Interface (FFI), allowing interoperability between Kotlin and Rust. This involved defining Rust functions in a way that they can be called from Kotlin code. We also had to ensure that data types were compatible and could be passed between the two languages. The Rust functions were then made available to Kotlin through the creation of a shared library (.so file) using the cargo build system. This library was integrated into the Android app, enabling it to generate and display TOTPs using the Rust code.

For the Android TOTP Rust Module, we created a Rust submodule specifically designed to interface with our core business logic in Rust. This involved taking the totp public module, which holds the shared secret key and provides the method for generating one-time passwords, and integrating it into a new Rust submodule tailored for Android. This allowed us to cross-compile the Rust code to be compatible with Android's architecture. Once the shared library (.so file) was generated, we could then integrate this library into our Android app. Consequently, our Kotlin application could interact with the Rust code, providing seamless integration between the two and enabling the Android app to generate and display TOTPs using the Rust code.

extern crate jni;

use jni::JNIEnv;
use jni::objects::{JClass, JString};

use totp_multiplatform_module::totp;

#[no_mangle]
pub extern "system" fn Java_id_co_ngecamp_totp_TOTP_getPinNumber<'local>(
    mut env: JNIEnv<'local>,
    _class: JClass<'local>,
    otp_digit: i32,
    time_threshold: i32,
    time_expiry: i64,
    secret: JString<'local>,
) -> JString<'local> {
    let secret: String = env
        .get_string(&secret)
        .expect("Couldn't get java string!")
        .into();
    let pin_number = totp::get_pin_number(otp_digit as usize, time_threshold as u8, time_expiry as u64, secret);
    let output = env
        .new_string(format!("{}", pin_number))
        .expect("Couldn't create java string!");
    output
}

In order to attach the Rust code to your Android project in folder android-totp-module, you will need to add the necessary Gradle dependency provided by Mozilla. This is done by including the following in your build.gradle file:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.mozilla.rust-android-gradle:plugin:0.9.3'
    }
}

Once this is done, you then need to apply the plugin to your Android module's build.gradle file:

apply plugin: 'com.android.application'
apply plugin: 'org.mozilla.rust-android-gradle.rust-android'

android {
    ...
}

rustAndroidModule {
    module = "../../totp_android"
    libname = "totp_android"
    targets = ["arm", "x86", "arm64", "x86_64"]
    pythonCommand = "python3"
}

tasks.configureEach { task ->
    if ((task.name == 'javaPreCompileDebug' || task.name == 'javaPreCompileRelease')) {
        task.dependsOn 'cargoBuild'
    }
}

To integrate the TOTP functionality into the Android application, we created a binary (.so) file which contains the implementation of the functions in the TOTP.kt class. This file was generated by compiling the Rust code using the cargo build system. Once the shared library was created, it was integrated into the Android app. This allowed the Kotlin application to interact with the Rust code, and call the functions defined in the TOTP.kt class. These functions are responsible for generating and displaying the Time-based One-Time Passwords (TOTP) in the Android app.

package id.co.ngecamp.totp

class TOTP {

    init {
        System.loadLibrary("totp_android")
    }
    
    private external fun getPinNumber(otp_digit:Int, time_threshold:Int, time_expiry:Int, secret:String): String?
    
    fun getPin(otp_digit:Int, time_threshold:Int, time_expiry:Int, secret:String): String? {
        return getPinNumber(otp_digit, time_threshold, time_expiry, secret)
    }

}

The totp-android-module closure specifies the module name, targets, library name, and optionally the NDK path. This setup allows your Android project to utilize the Rust code, enabling the TOTP functionality in your app.

With the Android project now completed, it can serve as a foundation for further development. It can be packaged into an Android library and shared across multiple Android applications. This allows other developers to leverage the Time-based One-Time Password (TOTP) functionality we've implemented without having to rewrite the underlying code. They can focus on the unique aspects of their application, saving time and reducing complexity. Alternatively, the project can be further developed into a standalone application. This could include adding user interface elements for generating and displaying TOTPs, or incorporating additional security features. The flexibility of the project opens up numerous possibilities for future development and expansion.

3. iOS TOTP Rust Module

To interface the TOTP module with our iOS project, we created a Rust Foreign Function Interface (FFI) designed specifically for iOS. This FFI enabled our Swift application to interact with the Rust code, providing a seamless integration between the two. Similar to the process for the Android TOTP Rust module, we created a shared library (.dylib file) using the cargo build system. This shared library was then integrated into our iOS application, allowing it to generate and display TOTPs using the Rust code. The Rust FFI for Swift resulted in an xcframework, which made it possible for our Swift-based iOS application to utilize the Rust TOTP module, thereby demonstrating the interoperability of Rust with other programming languages.

use std::os::raw::{c_char};
use std::ffi::{CString, CStr};

use totp_multiplatform_module::totp;

#[no_mangle]
pub extern fn get_pin_number(otp_digit: usize, time_threshold: u8, time_expiry: u64, secret: *const c_char) -> *mut c_char {
    let c_str = unsafe { CStr::from_ptr(secret) };
    let secret = match c_str.to_str() {
        Err(_) => "there is an error in the input string",
        Ok(string) => string,
    };
    let str = totp::get_pin_number(otp_digit, time_threshold, time_expiry, secret.to_string());
    CString::new(str.to_owned()).unwrap().into_raw()
}

Creating Makefile to Build iOS XCFramework

With our iOS TOTP module ready, the next step is to create a Makefile to automate the build process for the iOS XCFramework. This Makefile will specify how to derive the final iOS framework from our Rust code. It will include instructions on how to build the Rust code into a static library, how to combine the static libraries for different architectures into a single XCFramework, and how to clean up the build artifacts.

Here's an example of how such a Makefile might look:

macos:
	@cargo build --release --lib --target aarch64-apple-darwin
	@cargo build --release --lib --target x86_64-apple-darwin
	@$(RM) -rf libs/libtotp_ios-macos.a
	@$(RM) -rf libs/libtotp_ios-maccatalyst.a
	@lipo -create -output libs/libtotp_ios-macos.a \
		 target/aarch64-apple-darwin/release/libtotp_ios.a \
		 target/x86_64-apple-darwin/release/libtotp_ios.a

ios:
	@cargo build --release --lib --target aarch64-apple-ios
	@cargo build --release --lib --target aarch64-apple-ios-sim
	@cargo build --release --lib --target x86_64-apple-ios
	@$(RM) -rf libs/libtotp_ios.a
	@$(RM) -rf libs/libtotp_ios-sim.a
	@cp target/aarch64-apple-ios/release/libtotp_ios.a libs/libtotp_ios.a
	@lipo -create -output libs/libtotp_ios-sim.a \
		target/aarch64-apple-ios-sim/release/libtotp_ios.a \
		target/x86_64-apple-ios/release/libtotp_ios.a

You run this Makefile using the make all command in the terminal. This will automatically build the XCFramework for you, ready to be integrated into your iOS application.

Automating the Build Process with Shell Script

To automate the process of building and linking the necessary files, we can create a shell script. This script will execute the commands necessary to compile the Rust code, link the resulting library to the iOS project, and clean up any temporary files created during the process.

Here's an example of how such a shell script, named build-and-link-library.sh, might look:

#!/bin/sh

cargo lipo --release

mkdir -p include
mkdir -p libs

make ios
make macos
cbindgen src/lib.rs -l c > totp_ios.h

cp totp_ios.h include
rm totp_ios.h

if [ ! -f include/module.modulemap ]; then
    echo 'module TOTP {
      header "totp_ios.h"
      export *
    }' > include/module.modulemap
fi

if [ ! -f TOTP.xcframework ]; then
    xcodebuild -create-xcframework \
    -library libs/libtotp_ios-macos.a \
    -headers ./include/ \
    -library libs/libtotp_ios-sim.a \
    -headers ./include/ \
    -library libs/libtotp_ios.a \
    -headers ./include/ \
    -output TOTP.xcframework
fi

rm -rf include libs

This script can be made executable by running chmod +x build-and-link-library.sh in the terminal. Once it's executable, you can run the script with ./build-and-link-library.sh whenever you need to build and link the library. This helps to simplify and streamline the development process, especially when making frequent changes to the Rust code.

For the iOS project, we created an xcframework, a new framework format that allows us to bundle and distribute binary frameworks that support multiple platforms, architectures, and simulators. This process was made possible by Rust's interoperability with Swift.

To utilize the TOTP functionality in your iOS project, you need to import the previously created TOTP.xcframework into your project. You can do this by dragging and dropping the TOTP.xcframework file into the Frameworks, Libraries, and Embedded Content section of your project settings in Xcode.

Untitled

Once the framework is added to your project, you can use the TOTP functionality in your Swift code. Below is a sample code snippet that demonstrates this:

import SwiftUI
import TOTP

struct ContentView: View {
    let result: UnsafeMutablePointer<CChar> = TOTP.get_pin_number(6, 1, 30, "12345678901234567890")
    
    var swiftResult: String {
                String(cString: result)
            }
    
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text(swiftResult)
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

Here, TOTP.get_pin_number() is called to generate the one-time password, which is then displayed in the ContentView SwiftUI view.

By combining the Rust Foreign Function Interface (FFI) for Swift with the creation of a shared library (.dylib file), we were able to integrate the TOTP module into our iOS application seamlessly. The resulting xcframework not only enables our Swift-based iOS application to utilize the Rust TOTP module, but it also illustrates the interoperability of Rust with other programming languages. This xcframework can be packaged and distributed as a Swift library, which can be used across the entire Apple ecosystem. This opens up a wide range of possibilities for further development and expansion, allowing other developers to leverage the TOTP functionality without needing to rewrite the underlying Rust code.

4. RESTful API Web Server

In the server-side implementation of TOTP, two primary functions are performed: validation and enrollment of users for TOTP authentication. The server acts as the authoritative source of current time, and it also holds copies of all client secret keys.

The validation function is used to authenticate a user by comparing the TOTP generated by the user's device with the TOTP calculated by the server. The server takes the shared secret key and the current timestamp (which are both known to the server and the client), hashes them, and generates a TOTP. If the TOTP generated by the server matches the TOTP received from the user's device, the user is authenticated.

The enrollment function is used to register a user for TOTP authentication. During enrollment, the server generates a unique secret key for each user and communicates this key to the user's device. This secret key is stored on both the server and the user's device, and it is used to generate TOTPs for future authentication attempts.

The server-side implementation is crucial for the security and functionality of the TOTP authentication system. It ensures that TOTPs are validated correctly and that secret keys are distributed securely to users. For more details on the server-side implementation of TOTP, you can refer to the provided GitHub link.

5. Web Application Integration

For the web application integration of the TOTP module, we utilized a React.js application. This application interfaces with the Rust TOTP module and the server-side application to provide a user-friendly interface for TOTP authentication. Users can enroll for TOTP authentication, generate TOTPs, and authenticate themselves using the web application. The source code for the web application can be accessed at this GitHub link. This implementation serves as an example of how the TOTP module can be integrated into a web application, showcasing Rust's versatility in multiplatform development.

Untitled

Conclusion

In conclusion, Rust proves to be an invaluable tool in the development of multiplatform modules, demonstrating impressive flexibility and power. Its ability to interact seamlessly with other languages and systems, while maintaining high performance and memory safety, sets it apart. Through the creation of a Time-based One-Time Password (TOTP) module, we've shown how Rust can be employed to build a robust, secure, and reusable solution that can be easily integrated across server-side, mobile, and web applications. This exploration underscores the potential of Rust in revolutionizing multiplatform application development, paving the way for more efficient, secure, and versatile digital solutions. For the complete code, you can access it on my Github page here.

Reference

[1] https://github.com/constantoine/totp-rs

[2] https://mozilla.github.io/firefox-browser-architecture/experiments/2017-09-21-rust-on-android.html

[3] https://medium.com/@kennethyoel/a-swiftly-oxidizing-tutorial-44b86e8d84f5

[4] https://codevoweb.com/rust-implement-2fa-two-factor-authentication/

[5] https://codevoweb.com/two-factor-authentication-2fa-in-reactjs

About

This project demonstrates the use of Rust to develop a multiplatform Time-based One-Time Password (TOTP) module. It highlights Rust's high performance, memory safety, and interoperability with other languages for server-side, mobile, and web applications.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published