diff --git a/Cargo.toml b/Cargo.toml index b6b19c1..9208460 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ native-bindings = [] u2f-server = ["u2f", "webpki"] u2f = ["auth-base", "untrusted", "serde_repr", ] webauthn-server = [ "webauthn", "webpki" ] -webauthn = [ "auth-base", "bytes", "serde_cbor", "uuid", "http", "ed25519-dalek", "p256" ] +webauthn = [ "auth-base", "bytes", "serde_cbor", "uuid", "http", "ed25519-dalek", "p256", "indexmap" ] auth-base = ["base64", "byteorder", "ring", "serde", "serde_derive", "serde_json", "serde_bytes"] android = ["jni"] @@ -52,6 +52,7 @@ http = { version = "1.0", optional = true } uuid = { version = "1.6", optional = true } ed25519-dalek = { version = "2.1.0", features = ["rand_core", "pkcs8"], optional = true } p256 = { version = "0.13.2", optional = true } +indexmap = { version = "2.2.6", features = ["serde"], optional = true } [target.'cfg(target_os="android")'.dependencies] jni = { version = "0.20", default-features = false, optional = true } diff --git a/Slauth.podspec b/Slauth.podspec new file mode 100644 index 0000000..a0cab4e --- /dev/null +++ b/Slauth.podspec @@ -0,0 +1,19 @@ +Pod::Spec.new do |s| + s.name = 'Slauth' + s.version = '0.7.6' + s.summary = 'A Swift wrapper aroud Slauth Rust crate' + s.description = <<-DESC +TODO: Add long description of the pod here. + DESC + + s.homepage = 'https://github.com/Devolutions/Slauth.git' + s.license = { :type => 'MIT', :file => './LICENSE' } + s.author = { 'Devolutions' => 'lfauvel@devolutions.net' } + s.source = { :git => 'https://github.com/Devolutions/Slauth.git', :tag => s.version.to_s } + + s.swift_version = '5.0' + s.ios.deployment_target = '11.0' + + s.source_files = 'wrappers/swift/classes/**/*', 'slauth.h' + s.vendored_libraries = 'target/universal/release/*.a', 'target/x86_64-apple-io/release/*.a', 'target/aarch64-apple-ios/release/*.a' +end diff --git a/examples/web-server.rs b/examples/web-server.rs index be10f0d..b8ea99b 100644 --- a/examples/web-server.rs +++ b/examples/web-server.rs @@ -81,7 +81,7 @@ impl TestController { if let Some(context) = self.reg_contexts.read().expect("should be ok").get(&uuid) { let mut verifier = CredentialCreationVerifier::new(cred.clone(), context.clone(), "http://localhost"); if let Ok(result) = verifier.verify() { - self.creds.write().unwrap().insert(cred.id, (result.public_key, result.sign_count)); + self.creds.write().unwrap().insert(cred.id, (result.public_key, result.sign_count)); } } diff --git a/ios/build.sh b/ios/build.sh deleted file mode 100755 index 8cb0aa4..0000000 --- a/ios/build.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -cbindgen src/lib.rs -l c > slauth.h - -cargo lipo --targets \ -aarch64-apple-ios \ -armv7-apple-ios \ -armv7s-apple-ios \ -x86_64-apple-ios \ -i386-apple-ios --release - -mkdir ./target/ios - -cp ./target/aarch64-apple-ios/release/libslauth.a ./target/ios/libslauth_arm64.a -cp ./target/armv7-apple-ios/release/libslauth.a ./target/ios/libslauth_arm_v7.a -cp ./target/armv7s-apple-ios/release/libslauth.a ./target/ios/libslauth_arm_v7s.a -cp ./target/i386-apple-ios/release/libslauth.a ./target/ios/libslauth_i386.a -cp ./target/universal/release/libslauth.a ./target/ios/libslauth_universal.a -cp ./target/x86_64-apple-ios/release/libslauth.a ./target/ios/libslauth_x86.a \ No newline at end of file diff --git a/ios/setup.sh b/ios/setup.sh deleted file mode 100755 index c55a0be..0000000 --- a/ios/setup.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -rustup target add \ -aarch64-apple-ios \ -armv7-apple-ios \ -armv7s-apple-ios \ -x86_64-apple-ios \ -i386-apple-ios - -cargo install cbindgen -cargo install cargo-lipo \ No newline at end of file diff --git a/slauth.h b/slauth.h index f4ab699..c75c71c 100644 --- a/slauth.h +++ b/slauth.h @@ -3,207 +3,305 @@ #include #include -#define ASN1_DEFINITE_LONG_FOLLOWING_MASK 127 +#define OTP_DEFAULT_DIGITS_VALUE 6 + +#define HOTP_DEFAULT_COUNTER_VALUE 0 + +#define HOTP_DEFAULT_RESYNC_VALUE 2 + +#define TOTP_DEFAULT_PERIOD_VALUE 30 + +#define TOTP_DEFAULT_BACK_RESYNC_VALUE 1 + +#define TOTP_DEFAULT_FORWARD_RESYNC_VALUE 1 + +#define MAX_RESPONSE_LEN_SHORT 256 + +#define MAX_RESPONSE_LEN_EXTENDED 65536 + +#define ASN1_SEQ_TYPE 48 #define ASN1_DEFINITE_SHORT_MASK 128 +#define ASN1_DEFINITE_LONG_FOLLOWING_MASK 127 + #define ASN1_MAX_FOLLOWING_LEN_BYTES 126 -#define ASN1_SEQ_TYPE 48 +#define U2F_EC_KEY_SIZE 32 -#define CAPFLAG_WINK 1 +#define U2F_EC_POINT_SIZE ((U2F_EC_KEY_SIZE * 2) + 1) -#define CID_BROADCAST 4294967295 +#define U2F_MAX_KH_SIZE 128 -#define ERR_CHANNEL_BUSY 6 +#define U2F_MAX_ATT_CERT_SIZE 2048 -#define ERR_INVALID_CMD 1 +#define U2F_MAX_EC_SIG_SIZE 72 -#define ERR_INVALID_LEN 3 +#define U2F_CTR_SIZE 4 -#define ERR_INVALID_PAR 2 +#define U2F_APPID_SIZE 32 -#define ERR_INVALID_SEQ 4 +#define U2F_CHAL_SIZE 32 -#define ERR_LOCK_REQUIRED 10 +#define U2F_REGISTER_MAX_DATA_TBS_SIZE ((((1 + U2F_APPID_SIZE) + U2F_CHAL_SIZE) + U2F_MAX_KH_SIZE) + U2F_EC_POINT_SIZE) -#define ERR_MSG_TIMEOUT 5 +#define U2F_AUTH_MAX_DATA_TBS_SIZE ((((1 + U2F_APPID_SIZE) + U2F_CHAL_SIZE) + 1) + 4) -#define ERR_NONE 0 +#define U2F_POINT_UNCOMPRESSED 4 -#define ERR_OTHER 127 +#define U2F_REGISTER 1 -#define ERR_SYNC_FAIL 11 +#define U2F_AUTHENTICATE 2 -#define FIDO_USAGE_DATA_IN 32 +#define U2F_VERSION 3 -#define FIDO_USAGE_DATA_OUT 33 +#define U2F_VENDOR_FIRST 64 -#define FIDO_USAGE_PAGE 61904 +#define U2F_VENDOR_LAST 191 -#define FIDO_USAGE_U2FHID 1 +#define U2F_REGISTER_ID 5 -#define HID_RPT_SIZE 64 +#define U2F_REGISTER_HASH_ID 0 -#define HOTP_DEFAULT_COUNTER_VALUE 0 +#define U2F_AUTH_DONT_ENFORCE 8 -#define HOTP_DEFAULT_RESYNC_VALUE 2 +#define U2F_AUTH_ENFORCE 3 -#define INIT_NONCE_SIZE 8 +#define U2F_AUTH_CHECK_ONLY 7 -#define MAX_RESPONSE_LEN_EXTENDED 65536 +#define U2F_AUTH_FLAG_TUP 1 -#define MAX_RESPONSE_LEN_SHORT 256 +#define U2F_AUTH_FLAG_TDOWN 0 -#define OTP_DEFAULT_DIGITS_VALUE 6 +#define U2F_SW_NO_ERROR 36864 -#define TOTP_DEFAULT_BACK_RESYNC_VALUE 1 +#define U2F_SW_WRONG_DATA 27264 -#define TOTP_DEFAULT_FORWARD_RESYNC_VALUE 1 +#define U2F_SW_CONDITIONS_NOT_SATISFIED 27013 -#define TOTP_DEFAULT_PERIOD_VALUE 30 +#define U2F_SW_COMMAND_NOT_ALLOWED 27014 -#define TYPE_CONT 0 +#define U2F_SW_WRONG_LENGTH 26368 -#define TYPE_INIT 128 +#define U2F_SW_CLA_NOT_SUPPORTED 28160 + +#define U2F_SW_INS_NOT_SUPPORTED 27904 + +#define HID_RPT_SIZE 64 + +#define CID_BROADCAST 4294967295 #define TYPE_MASK 128 +#define TYPE_INIT 128 + +#define TYPE_CONT 0 + +#define FIDO_USAGE_PAGE 61904 + +#define FIDO_USAGE_U2FHID 1 + +#define FIDO_USAGE_DATA_IN 32 + +#define FIDO_USAGE_DATA_OUT 33 + #define U2FHID_IF_VERSION 2 #define U2FHID_TRANS_TIMEOUT 3000 -#define U2F_APPID_SIZE 32 +#define U2FHID_PING (TYPE_INIT | 1) -#define U2F_AUTHENTICATE 2 +#define U2FHID_MSG (TYPE_INIT | 3) -#define U2F_AUTH_CHECK_ONLY 7 +#define U2FHID_LOCK (TYPE_INIT | 4) -#define U2F_AUTH_DONT_ENFORCE 8 +#define U2FHID_INIT (TYPE_INIT | 6) -#define U2F_AUTH_ENFORCE 3 +#define U2FHID_WINK (TYPE_INIT | 8) -#define U2F_AUTH_FLAG_TDOWN 0 +#define U2FHID_SYNC (TYPE_INIT | 60) -#define U2F_AUTH_FLAG_TUP 1 +#define U2FHID_ERROR (TYPE_INIT | 63) -#define U2F_CHAL_SIZE 32 +#define U2FHID_VENDOR_FIRST (TYPE_INIT | 64) -#define U2F_CTR_SIZE 4 +#define U2FHID_VENDOR_LAST (TYPE_INIT | 127) -#define U2F_EC_KEY_SIZE 32 +#define INIT_NONCE_SIZE 8 -#define U2F_MAX_ATT_CERT_SIZE 2048 +#define CAPFLAG_WINK 1 -#define U2F_MAX_EC_SIG_SIZE 72 +#define ERR_NONE 0 -#define U2F_MAX_KH_SIZE 128 +#define ERR_INVALID_CMD 1 -#define U2F_POINT_UNCOMPRESSED 4 +#define ERR_INVALID_PAR 2 -#define U2F_REGISTER 1 +#define ERR_INVALID_LEN 3 -#define U2F_REGISTER_HASH_ID 0 +#define ERR_INVALID_SEQ 4 -#define U2F_REGISTER_ID 5 +#define ERR_MSG_TIMEOUT 5 -#define U2F_SW_CLA_NOT_SUPPORTED 28160 +#define ERR_CHANNEL_BUSY 6 -#define U2F_SW_COMMAND_NOT_ALLOWED 27014 +#define ERR_LOCK_REQUIRED 10 -#define U2F_SW_CONDITIONS_NOT_SATISFIED 27013 +#define ERR_SYNC_FAIL 11 -#define U2F_SW_INS_NOT_SUPPORTED 27904 +#define ERR_OTHER 127 -#define U2F_SW_NO_ERROR 36864 +#define WEBAUTHN_CHALLENGE_LENGTH 32 -#define U2F_SW_WRONG_DATA 27264 +#define WEBAUTHN_CREDENTIAL_ID_LENGTH 16 -#define U2F_SW_WRONG_LENGTH 26368 +#define WEBAUTHN_USER_PRESENT_FLAG 1 -#define U2F_VENDOR_FIRST 64 +#define WEBAUTHN_USER_VERIFIED_FLAG 4 -#define U2F_VENDOR_LAST 191 +#define WEBAUTHN_ATTESTED_CREDENTIAL_DATA_FLAG 64 -#define U2F_VERSION 3 +#define WEBAUTHN_EXTENSION_DATA_FLAG 128 + +#define WEBAUTH_PUBLIC_KEY_TYPE_OKP 1 + +#define WEBAUTH_PUBLIC_KEY_TYPE_EC2 2 + +#define WEBAUTH_PUBLIC_KEY_TYPE_RSA 3 + +#define ECDSA_Y_PREFIX_POSITIVE 2 + +#define ECDSA_Y_PREFIX_NEGATIVE 3 + +#define ECDSA_Y_PREFIX_UNCOMPRESSED 4 + +#define ECDSA_CURVE_P256 1 + +#define ECDSA_CURVE_P384 2 + +#define ECDSA_CURVE_P521 3 + +#define ECDAA_CURVE_ED25519 6 + +#define TPM_GENERATED_VALUE 4283712327 + +typedef struct AuthenticatorCreationResponse AuthenticatorCreationResponse; + +typedef struct AuthenticatorRequestResponse AuthenticatorRequestResponse; typedef struct ClientWebResponse ClientWebResponse; typedef struct HOTPContext HOTPContext; +typedef struct HashesAlgorithm HashesAlgorithm; + typedef struct SigningKey SigningKey; typedef struct TOTPContext TOTPContext; +/** + * + */ typedef struct U2fRequest U2fRequest; -typedef U2fRequest WebRequest; - -void client_web_response_free(ClientWebResponse *rsp); - -SigningKey *client_web_response_signing_key(ClientWebResponse *rsp); - -char *client_web_response_to_json(ClientWebResponse *rsp); +typedef struct U2fRequest WebRequest; -void hotp_free(HOTPContext *hotp); +typedef struct Buffer { + uint8_t *data; + uintptr_t len; +} Buffer; -HOTPContext *hotp_from_uri(const char *uri); -char *hotp_gen(HOTPContext *hotp); -void hotp_inc(HOTPContext *hotp); +struct HOTPContext *hotp_from_uri(const char *uri); -char *hotp_to_uri(HOTPContext *hotp, const char *label, const char *issuer); +void hotp_free(struct HOTPContext *hotp); -bool hotp_validate_current(HOTPContext *hotp, const char *code); +char *hotp_to_uri(struct HOTPContext *hotp, const char *label, const char *issuer); -bool hotp_verify(HOTPContext *hotp, const char *code); +char *hotp_gen(struct HOTPContext *hotp); -void signing_key_free(SigningKey *s); +void hotp_inc(struct HOTPContext *hotp); -SigningKey *signing_key_from_string(const char *s); +bool hotp_verify(struct HOTPContext *hotp, const char *code); -char *signing_key_get_key_handle(SigningKey *s); +bool hotp_validate_current(struct HOTPContext *hotp, const char *code); -char *signing_key_to_string(SigningKey *s); +struct TOTPContext *totp_from_uri(const char *uri); -void totp_free(TOTPContext *totp); +void totp_free(struct TOTPContext *totp); -TOTPContext *totp_from_uri(const char *uri); +char *totp_to_uri(struct TOTPContext *totp, const char *label, const char *issuer); -char *totp_gen(TOTPContext *totp); +char *totp_gen(struct TOTPContext *totp); -char *totp_gen_with(TOTPContext *totp, unsigned long elapsed); +char *totp_gen_with(struct TOTPContext *totp, unsigned long elapsed); -char *totp_to_uri(TOTPContext *totp, const char *label, const char *issuer); +bool totp_verify(struct TOTPContext *totp, const char *code); -bool totp_validate_current(TOTPContext *totp, const char *code); +bool totp_validate_current(struct TOTPContext *totp, const char *code); -bool totp_verify(TOTPContext *totp, const char *code); +WebRequest *web_request_from_json(const char *req); void web_request_free(WebRequest *req); -WebRequest *web_request_from_json(const char *req); - bool web_request_is_register(WebRequest *req); bool web_request_is_sign(WebRequest *req); +char *web_request_origin(WebRequest *req); + +unsigned long long web_request_timeout(WebRequest *req); + char *web_request_key_handle(WebRequest *req, const char *origin); -char *web_request_origin(WebRequest *req); +struct ClientWebResponse *web_request_sign(WebRequest *req, + struct SigningKey *signing_key, + const char *origin, + unsigned long counter, + bool user_presence); -ClientWebResponse *web_request_register(WebRequest *req, - const char *origin, - const unsigned char *attestation_cert, - unsigned long long attestation_cert_len, - const unsigned char *attestation_key, - unsigned long long attestation_key_len); +struct ClientWebResponse *web_request_register(WebRequest *req, + const char *origin, + const unsigned char *attestation_cert, + unsigned long long attestation_cert_len, + const unsigned char *attestation_key, + unsigned long long attestation_key_len); -ClientWebResponse *web_request_sign(WebRequest *req, - SigningKey *signing_key, - const char *origin, - unsigned long counter, - bool user_presence); +void client_web_response_free(struct ClientWebResponse *rsp); -unsigned long long web_request_timeout(WebRequest *req); +char *client_web_response_to_json(struct ClientWebResponse *rsp); + +struct SigningKey *client_web_response_signing_key(struct ClientWebResponse *rsp); + +void signing_key_free(struct SigningKey *s); + +char *signing_key_to_string(struct SigningKey *s); + +char *signing_key_get_key_handle(struct SigningKey *s); + +struct SigningKey *signing_key_from_string(const char *s); + +char *get_private_key_from_response(struct AuthenticatorCreationResponse *res); + +struct Buffer get_attestation_object_from_response(struct AuthenticatorCreationResponse *res); + +void response_free(struct AuthenticatorCreationResponse *res); + +struct AuthenticatorCreationResponse *generate_credential_creation_response(const char *aaguid, + const unsigned char *credential_id, + uintptr_t credential_id_length, + const char *rp_id, + uint8_t attestation_flags, + const int *cose_algorithm_identifiers, + uintptr_t cose_algorithm_identifiers_length); + +struct AuthenticatorRequestResponse *generate_credential_request_response(const char *rp_id, + const char *private_key, + uint8_t attestation_flags, + const unsigned char *client_data_hash, + uintptr_t client_data_hash_length); + +struct Buffer get_auth_data_from_response(struct AuthenticatorRequestResponse *res); + +struct Buffer get_signature_from_response(struct AuthenticatorRequestResponse *res); diff --git a/src/oath/hotp.rs b/src/oath/hotp.rs index c3da9e5..8276957 100644 --- a/src/oath/hotp.rs +++ b/src/oath/hotp.rs @@ -257,11 +257,11 @@ mod native_bindings { #[no_mangle] pub unsafe extern "C" fn hotp_from_uri(uri: *const c_char) -> *mut HOTPContext { let uri_str = strings::c_char_to_string(uri); - Box::into_raw( - HOTPContext::from_uri(&uri_str) - .map(Box::new) - .unwrap_or_else(|_| Box::from_raw(null_mut())), - ) + let hotp = HOTPContext::from_uri(&uri_str).map(Box::new); + match hotp { + Ok(hotp) => Box::into_raw(hotp), + Err(_) => null_mut(), + } } #[no_mangle] diff --git a/src/oath/totp.rs b/src/oath/totp.rs index 4a9de90..1de71ed 100644 --- a/src/oath/totp.rs +++ b/src/oath/totp.rs @@ -326,11 +326,12 @@ mod native_bindings { #[no_mangle] pub unsafe extern "C" fn totp_from_uri(uri: *const c_char) -> *mut TOTPContext { let uri_str = strings::c_char_to_string(uri); - Box::into_raw( - TOTPContext::from_uri(&uri_str) - .map(Box::new) - .unwrap_or_else(|_| Box::from_raw(null_mut())), - ) + let totp = TOTPContext::from_uri(&uri_str).map(Box::new); + + match totp { + Ok(totp) => Box::into_raw(totp), + Err(_) => null_mut(), + } } #[no_mangle] diff --git a/src/webauthn/authenticator/mod.rs b/src/webauthn/authenticator/mod.rs index f65d536..a1f2aab 100644 --- a/src/webauthn/authenticator/mod.rs +++ b/src/webauthn/authenticator/mod.rs @@ -1,5 +1,8 @@ pub(crate) mod responses; +#[cfg(feature = "native-bindings")] +pub(crate) mod native; + use crate::webauthn::{ authenticator::responses::AuthenticatorCredentialCreationResponse, error::Error, @@ -9,9 +12,7 @@ use crate::webauthn::{ AttestationFlags, AttestationObject, AttestedCredentialData, AuthenticatorData, Coordinates, CoseAlgorithmIdentifier, CoseKeyInfo, CredentialPublicKey, Message, Rsa, EC2, OKP, }, - web_message::{ - CollectedClientData, PublicKeyCredentialCreationOptions, PublicKeyCredentialParameters, UserVerificationRequirement, - }, + web_message::{CollectedClientData, PublicKeyCredentialCreationOptions, UserVerificationRequirement}, }, }; use base64::URL_SAFE_NO_PAD; @@ -132,10 +133,57 @@ impl WebauthnAuthenticator { .as_ref() .or(binding.as_ref()) .ok_or(WebauthnCredentialRequestError::RpIdOrOriginRequired)?; - let mut hasher = Sha256::new(); - hasher.update(rp_id); - let alg = Self::find_best_supported_algorithm(&credential_creation_options.pub_key_cred_params)?; + let algs: Vec = credential_creation_options + .pub_key_cred_params + .into_iter() + .map(|x| x.alg.into()) + .collect(); + let alg = Self::find_best_supported_algorithm(algs.as_slice())?; + + let (attestation_object, private_key_response, der) = + Self::generate_attestation_object(alg, aaguid, &credential_id, rp_id, attestation_flags)?; + + let challenge = base64::decode(credential_creation_options.challenge)?; + let collected_client_data = CollectedClientData { + request_type: WEBAUTHN_REQUEST_TYPE_CREATE.to_owned(), + challenge: base64::encode_config(challenge, URL_SAFE_NO_PAD), + origin: origin.as_ref().unwrap_or(rp_id).clone(), + cross_origin: false, + token_binding: None, + }; + + let auth_data = attestation_object.auth_data.clone(); + let credential = PublicKeyCredentialRaw { + id: base64::encode_config(credential_id.clone(), URL_SAFE_NO_PAD), + raw_id: credential_id, + response: Some(AuthenticatorAttestationResponseRaw { + attestation_object: Some(attestation_object.to_bytes()?), + client_data_json: serde_json::to_string(&collected_client_data)?.into_bytes(), + authenticator_data: auth_data.to_vec().ok(), + signature: None, + user_handle: None, + transports: vec![Transport::Internal], + }), + }; + + Ok(AuthenticatorCredentialCreationResponse { + credential_response: credential, + private_key_response, + additional_data: AuthenticatorCredentialCreationResponseAdditionalData { + public_key_der: der, + public_key_alg: alg.into(), + }, + }) + } + + pub fn generate_attestation_object( + alg: CoseAlgorithmIdentifier, + aaguid: Uuid, + credential_id: &Vec, + rp_id: &str, + attestation_flags: u8, + ) -> Result<(AttestationObject, String, Vec), WebauthnCredentialRequestError> { let (key_info, private_key_response, der) = match alg { CoseAlgorithmIdentifier::Ed25519 => { let keypair = ed25519_dalek::SigningKey::generate(&mut OsRng); @@ -157,7 +205,7 @@ impl WebauthnAuthenticator { ) } - CoseAlgorithmIdentifier::EC2 => { + CoseAlgorithmIdentifier::ES256 => { let secret_key = p256::ecdsa::SigningKey::random(&mut OsRng); let verifying_key = VerifyingKey::from(&secret_key); let points = verifying_key.to_encoded_point(false); @@ -213,56 +261,18 @@ impl WebauthnAuthenticator { None }; - let auth_data = AuthenticatorData { - rp_id_hash: hasher - .finalize_reset() - .to_vec() - .try_into() - .map_err(|e: Vec| WebauthnCredentialRequestError::RpIdHashInvalidLength(e.len()))?, - flags: attestation_flags, - sign_count: 0, - attested_credential_data, - extensions: Value::Null, - }; - - let attestation_object = AttestationObject { - auth_data: auth_data.clone(), - raw_auth_data: vec![], - fmt: WEBAUTHN_FORMAT_NONE.to_owned(), - att_stmt: Some(AttestationStatement::None), - } - .to_bytes()?; - - let challenge = base64::decode(credential_creation_options.challenge)?; - let collected_client_data = CollectedClientData { - request_type: WEBAUTHN_REQUEST_TYPE_CREATE.to_owned(), - challenge: base64::encode_config(challenge, URL_SAFE_NO_PAD), - origin: origin.as_ref().unwrap_or(rp_id).clone(), - cross_origin: false, - token_binding: None, - }; - - let credential = PublicKeyCredentialRaw { - id: base64::encode_config(credential_id.clone(), URL_SAFE_NO_PAD), - raw_id: credential_id, - response: Some(AuthenticatorAttestationResponseRaw { - attestation_object: Some(attestation_object), - client_data_json: serde_json::to_string(&collected_client_data)?.into_bytes(), - authenticator_data: auth_data.to_vec().ok(), - signature: None, - user_handle: None, - transports: vec![Transport::Internal], - }), - }; + let auth_data = Self::generate_authenticator_data(rp_id, attestation_flags, attested_credential_data)?; - Ok(AuthenticatorCredentialCreationResponse { - credential_response: credential, - private_key_response, - additional_data: AuthenticatorCredentialCreationResponseAdditionalData { - public_key_der: der, - public_key_alg: alg.into(), + Ok(( + AttestationObject { + auth_data: auth_data.clone(), + raw_auth_data: vec![], + fmt: WEBAUTHN_FORMAT_NONE.to_owned(), + att_stmt: Some(AttestationStatement::None), }, - }) + private_key_response, + der, + )) } pub fn generate_credential_request_response( @@ -289,21 +299,8 @@ impl WebauthnAuthenticator { .as_ref() .or(binding.as_ref()) .ok_or(WebauthnCredentialRequestError::RpIdOrOriginRequired)?; - let mut hasher = Sha256::new(); - hasher.update(rp_id); - let auth_data = AuthenticatorData { - rp_id_hash: hasher - .finalize_reset() - .to_vec() - .try_into() - .map_err(|e: Vec| WebauthnCredentialRequestError::RpIdHashInvalidLength(e.len()))?, - flags: attestation_flags, - sign_count: 0, - attested_credential_data: None, - extensions: Value::Null, - }; - let auth_data_bytes = auth_data.to_vec()?; + let auth_data_bytes = Self::generate_authenticator_data(rp_id, attestation_flags, None)?.to_vec()?; let challenge = base64::decode(credential_request_options.challenge)?; let collected_client_data = CollectedClientData { @@ -318,27 +315,7 @@ impl WebauthnAuthenticator { hasher.update(client_data_bytes.as_slice()); let hash = hasher.finalize_reset().to_vec(); - let private_key_response: PrivateKeyResponse = serde_cbor::from_slice(&base64::decode(private_key)?)?; - - let signature = match private_key_response.key_alg { - CoseAlgorithmIdentifier::Ed25519 => { - let key = ed25519_dalek::SigningKey::try_from(private_key_response.private_key.as_slice())?; - key.sign([auth_data_bytes.as_slice(), hash.as_slice()].concat().as_slice()).to_vec() - } - CoseAlgorithmIdentifier::EC2 => { - let key = p256::ecdsa::SigningKey::try_from(private_key_response.private_key.as_slice())?; - let (sig, _) = key.sign([auth_data_bytes.as_slice(), hash.as_slice()].concat().as_slice()); - sig.to_der().to_vec() - } - CoseAlgorithmIdentifier::RSA => { - let key = rsa::RsaPrivateKey::from_pkcs1_der(&private_key_response.private_key)?; - let signing_key = rsa::pkcs1v15::SigningKey::::new(key); - signing_key - .sign([auth_data_bytes.as_slice(), hash.as_slice()].concat().as_slice()) - .to_vec() - } - _ => return Err(WebauthnCredentialRequestError::AlgorithmNotSupported), - }; + let signature = Self::generate_signature(auth_data_bytes.as_slice(), hash.as_slice(), private_key)?; Ok(PublicKeyCredentialRaw { id: base64::encode_config(credential_id.clone(), URL_SAFE_NO_PAD), @@ -354,23 +331,67 @@ impl WebauthnAuthenticator { }) } + pub fn generate_authenticator_data( + rp_id: &str, + attestation_flags: u8, + attested_credential_data: Option, + ) -> Result { + let mut hasher = Sha256::new(); + hasher.update(rp_id); + + Ok(AuthenticatorData { + rp_id_hash: hasher + .finalize_reset() + .to_vec() + .try_into() + .map_err(|e: Vec| WebauthnCredentialRequestError::RpIdHashInvalidLength(e.len()))?, + flags: attestation_flags, + sign_count: 0, + attested_credential_data, + extensions: Value::Null, + }) + } + + pub fn generate_signature( + auth_data_bytes: &[u8], + client_data_hash: &[u8], + private_key: String, + ) -> Result, WebauthnCredentialRequestError> { + let private_key_response: PrivateKeyResponse = serde_cbor::from_slice(&base64::decode(private_key)?)?; + + match private_key_response.key_alg { + CoseAlgorithmIdentifier::Ed25519 => { + let key = ed25519_dalek::SigningKey::try_from(private_key_response.private_key.as_slice())?; + Ok(key.sign([auth_data_bytes, client_data_hash].concat().as_slice()).to_vec()) + } + CoseAlgorithmIdentifier::ES256 => { + let key = p256::ecdsa::SigningKey::try_from(private_key_response.private_key.as_slice())?; + let (sig, _) = key.sign([auth_data_bytes, client_data_hash].concat().as_slice()); + Ok(sig.to_der().to_vec()) + } + CoseAlgorithmIdentifier::RSA => { + let key = rsa::RsaPrivateKey::from_pkcs1_der(&private_key_response.private_key)?; + let signing_key = rsa::pkcs1v15::SigningKey::::new(key); + Ok(signing_key.sign([auth_data_bytes, client_data_hash].concat().as_slice()).to_vec()) + } + _ => return Err(WebauthnCredentialRequestError::AlgorithmNotSupported), + } + } + fn find_best_supported_algorithm( - pub_key_cred_params: &[PublicKeyCredentialParameters], + pub_key_cred_params: &[CoseAlgorithmIdentifier], ) -> Result { //Order of preference for credential type is: Ed25519 > EC2 > RSA > RS1 let mut possible_credential_types = vec![ CoseAlgorithmIdentifier::RSA, - CoseAlgorithmIdentifier::EC2, + CoseAlgorithmIdentifier::ES256, CoseAlgorithmIdentifier::Ed25519, ]; let mut best_alg_index = None; let iterator = pub_key_cred_params.iter(); for param in iterator { - if let Some(alg_index) = possible_credential_types - .iter() - .position(|r| *r == CoseAlgorithmIdentifier::from(param.alg)) - { + if let Some(alg_index) = possible_credential_types.iter().position(|r| r == param) { if best_alg_index.filter(|x| x > &alg_index).is_none() { best_alg_index = Some(alg_index); } @@ -391,51 +412,30 @@ impl WebauthnAuthenticator { #[test] fn test_best_alg() { let params = vec![ - PublicKeyCredentialParameters { - auth_type: PublicKeyCredentialType::PublicKey, - alg: CoseAlgorithmIdentifier::Ed25519.into(), - }, - PublicKeyCredentialParameters { - auth_type: PublicKeyCredentialType::PublicKey, - alg: CoseAlgorithmIdentifier::EC2.into(), - }, - PublicKeyCredentialParameters { - auth_type: PublicKeyCredentialType::PublicKey, - alg: CoseAlgorithmIdentifier::RS1.into(), - }, - PublicKeyCredentialParameters { - auth_type: PublicKeyCredentialType::PublicKey, - alg: CoseAlgorithmIdentifier::RSA.into(), - }, + CoseAlgorithmIdentifier::Ed25519, + CoseAlgorithmIdentifier::ES256, + CoseAlgorithmIdentifier::RS1, + CoseAlgorithmIdentifier::RSA, ]; let alg = WebauthnAuthenticator::find_best_supported_algorithm(¶ms).unwrap(); assert_eq!(alg, CoseAlgorithmIdentifier::Ed25519); let params2 = vec![ - PublicKeyCredentialParameters { - auth_type: PublicKeyCredentialType::PublicKey, - alg: CoseAlgorithmIdentifier::EC2.into(), - }, - PublicKeyCredentialParameters { - auth_type: PublicKeyCredentialType::PublicKey, - alg: CoseAlgorithmIdentifier::RS1.into(), - }, - PublicKeyCredentialParameters { - auth_type: PublicKeyCredentialType::PublicKey, - alg: CoseAlgorithmIdentifier::RSA.into(), - }, + CoseAlgorithmIdentifier::ES256.into(), + CoseAlgorithmIdentifier::RS1.into(), + CoseAlgorithmIdentifier::RSA.into(), ]; let alg = WebauthnAuthenticator::find_best_supported_algorithm(¶ms2).unwrap(); - assert_eq!(alg, CoseAlgorithmIdentifier::EC2); + assert_eq!(alg, CoseAlgorithmIdentifier::ES256); } #[test] fn test_credential_generation() { for alg in [ CoseAlgorithmIdentifier::Ed25519, - CoseAlgorithmIdentifier::EC2, + CoseAlgorithmIdentifier::ES256, CoseAlgorithmIdentifier::RSA, ] { let user_uuid = Uuid::new_v4(); @@ -452,7 +452,7 @@ fn test_credential_generation() { display_name: "test".to_owned(), icon: None, }, - pub_key_cred_params: vec![PublicKeyCredentialParameters { + pub_key_cred_params: vec![crate::webauthn::proto::web_message::PublicKeyCredentialParameters { auth_type: PublicKeyCredentialType::PublicKey, alg: alg.into(), }], diff --git a/src/webauthn/authenticator/native.rs b/src/webauthn/authenticator/native.rs new file mode 100644 index 0000000..d02c1a9 --- /dev/null +++ b/src/webauthn/authenticator/native.rs @@ -0,0 +1,177 @@ +#[cfg(target_os = "ios")] +mod ios { + use crate::{ + strings, + webauthn::{ + authenticator::WebauthnAuthenticator, + proto::raw_message::{CoseAlgorithmIdentifier, Message}, + }, + }; + use std::{ + ffi::{c_int, c_uchar, CString}, + os::raw::c_char, + ptr::null_mut, + }; + use uuid::Uuid; + + pub struct AuthenticatorCreationResponse { + pub private_key_response: String, + pub attestation_object: Vec, + } + + pub struct AuthenticatorRequestResponse { + pub auth_data_bytes: Vec, + pub signature: Vec, + } + + #[repr(C)] + pub struct Buffer { + data: *mut u8, + len: usize, + } + + #[no_mangle] + pub unsafe extern "C" fn get_private_key_from_response(res: *mut AuthenticatorCreationResponse) -> *mut c_char { + if res.is_null() { + return null_mut(); + } + + let cstring = CString::new((*res).private_key_response.clone()); + match cstring { + Ok(cstring) => cstring.into_raw(), + Err(_) => null_mut(), + } + } + + #[no_mangle] + pub unsafe extern "C" fn get_attestation_object_from_response(res: *mut AuthenticatorCreationResponse) -> Buffer { + if res.is_null() { + return Buffer { data: null_mut(), len: 0 }; + } + + Buffer { + data: (*res).attestation_object.as_mut_ptr(), + len: (*res).attestation_object.len(), + } + } + + #[no_mangle] + pub unsafe extern "C" fn response_free(res: *mut AuthenticatorCreationResponse) { + let _ = Box::from_raw(res); + } + + #[no_mangle] + pub unsafe extern "C" fn generate_credential_creation_response( + aaguid: *const c_char, + credential_id: *const c_uchar, + credential_id_length: usize, + rp_id: *const c_char, + attestation_flags: u8, + cose_algorithm_identifiers: *const c_int, + cose_algorithm_identifiers_length: usize, + ) -> *mut AuthenticatorCreationResponse { + let aaguid_str = strings::c_char_to_string(aaguid); + let aaguid = Uuid::parse_str(aaguid_str.as_str()); + if aaguid.is_err() { + return null_mut(); + } + + let rp_id_str = strings::c_char_to_string(rp_id); + + let credential_id: Vec = std::slice::from_raw_parts(credential_id, credential_id_length).into(); + let algorithms_raw: Vec = std::slice::from_raw_parts(cose_algorithm_identifiers, cose_algorithm_identifiers_length).into(); + let alg = WebauthnAuthenticator::find_best_supported_algorithm( + algorithms_raw + .into_iter() + .map(CoseAlgorithmIdentifier::from) + .collect::>() + .as_slice(), + ); + if alg.is_err() { + return null_mut(); + } + + let attestation_object = WebauthnAuthenticator::generate_attestation_object( + alg.expect("Checked above"), + aaguid.expect("Checked above"), + &credential_id, + rp_id_str.as_str(), + u8::from_be(attestation_flags), + ); + + if attestation_object.is_err() { + return null_mut(); + } + + let (attestation_object, private_key, _) = attestation_object.expect("Checked above"); + let attestation_object_bytes = attestation_object.to_bytes(); + if attestation_object_bytes.is_err() { + return null_mut(); + } + + Box::into_raw(Box::new(AuthenticatorCreationResponse { + private_key_response: private_key, + attestation_object: attestation_object_bytes.expect("Checked above"), + })) + } + + #[no_mangle] + pub unsafe extern "C" fn generate_credential_request_response( + rp_id: *const c_char, + private_key: *const c_char, + attestation_flags: u8, + client_data_hash: *const c_uchar, + client_data_hash_length: usize, + ) -> *mut AuthenticatorRequestResponse { + let rp_id_str = strings::c_char_to_string(rp_id); + let private_key = strings::c_char_to_string(private_key); + let client_data_hash: Vec = std::slice::from_raw_parts(client_data_hash, client_data_hash_length).into(); + + let auth_data = WebauthnAuthenticator::generate_authenticator_data(rp_id_str.as_str(), u8::from_be(attestation_flags), None); + + if auth_data.is_err() { + return null_mut(); + } + + let auth_data_bytes = auth_data.expect("Checked above").to_vec(); + if auth_data_bytes.is_err() { + return null_mut(); + } + let auth_data_bytes = auth_data_bytes.expect("Checked above"); + + let signature = WebauthnAuthenticator::generate_signature(auth_data_bytes.as_slice(), client_data_hash.as_slice(), private_key); + + if signature.is_err() { + return null_mut(); + } + + Box::into_raw(Box::new(AuthenticatorRequestResponse { + auth_data_bytes, + signature: signature.expect("Checked above"), + })) + } + + #[no_mangle] + pub unsafe extern "C" fn get_auth_data_from_response(res: *mut AuthenticatorRequestResponse) -> Buffer { + if res.is_null() { + return Buffer { data: null_mut(), len: 0 }; + } + + Buffer { + data: (*res).auth_data_bytes.as_mut_ptr(), + len: (*res).auth_data_bytes.len(), + } + } + + #[no_mangle] + pub unsafe extern "C" fn get_signature_from_response(res: *mut AuthenticatorRequestResponse) -> Buffer { + if res.is_null() { + return Buffer { data: null_mut(), len: 0 }; + } + + Buffer { + data: (*res).signature.as_mut_ptr(), + len: (*res).signature.len(), + } + } +} diff --git a/src/webauthn/proto/raw_message.rs b/src/webauthn/proto/raw_message.rs index aca1741..be51766 100644 --- a/src/webauthn/proto/raw_message.rs +++ b/src/webauthn/proto/raw_message.rs @@ -11,6 +11,7 @@ use crate::webauthn::{ }; use byteorder::{BigEndian, ReadBytesExt}; use bytes::Buf; +use indexmap::IndexMap; use serde_cbor::{to_vec, Value}; use serde_derive::*; use std::{ @@ -272,7 +273,7 @@ impl CredentialPublicKey { .ok_or_else(|| Error::Other("algorithm missing".to_string()))?; match (key_type, CoseAlgorithmIdentifier::from(alg)) { - (WEBAUTH_PUBLIC_KEY_TYPE_EC2, CoseAlgorithmIdentifier::EC2) => { + (WEBAUTH_PUBLIC_KEY_TYPE_EC2, CoseAlgorithmIdentifier::ES256) => { let curve = map .get(&Value::Integer(-1)) .map(|val| match val { @@ -412,21 +413,21 @@ impl CredentialPublicKey { } pub fn to_bytes(self) -> Result, Error> { - let mut map = BTreeMap::new(); + let mut map = IndexMap::new(); match self.key_info { CoseKeyInfo::EC2(value) => { - map.insert(Value::Integer(1), Value::Integer(WEBAUTH_PUBLIC_KEY_TYPE_EC2 as i128)); - map.insert(Value::Integer(3), Value::Integer(CoseAlgorithmIdentifier::EC2 as i128)); - map.insert(Value::Integer(-1), Value::Integer(value.curve as i128)); + map.insert(1, Value::Integer(WEBAUTH_PUBLIC_KEY_TYPE_EC2 as i128)); + map.insert(3, Value::Integer(self.alg as i128)); + map.insert(-1, Value::Integer(value.curve as i128)); match value.coords { Coordinates::Compressed { x, y } => { - map.insert(Value::Integer(-2), Value::Bytes(x.to_vec())); - map.insert(Value::Integer(-3), Value::Bool(y == ECDSA_Y_PREFIX_NEGATIVE)); + map.insert(-2, Value::Bytes(x.to_vec())); + map.insert(-3, Value::Bool(y == ECDSA_Y_PREFIX_NEGATIVE)); } Coordinates::Uncompressed { x, y } => { - map.insert(Value::Integer(-2), Value::Bytes(x.to_vec())); - map.insert(Value::Integer(-3), Value::Bytes(y.to_vec())); + map.insert(-2, Value::Bytes(x.to_vec())); + map.insert(-3, Value::Bytes(y.to_vec())); } Coordinates::None => { @@ -436,18 +437,18 @@ impl CredentialPublicKey { } CoseKeyInfo::OKP(value) => { - map.insert(Value::Integer(1), Value::Integer(WEBAUTH_PUBLIC_KEY_TYPE_OKP as i128)); - map.insert(Value::Integer(3), Value::Integer(CoseAlgorithmIdentifier::Ed25519 as i128)); - map.insert(Value::Integer(-1), Value::Integer(value.curve as i128)); + map.insert(1, Value::Integer(WEBAUTH_PUBLIC_KEY_TYPE_OKP as i128)); + map.insert(3, Value::Integer(self.alg as i128)); + map.insert(-1, Value::Integer(value.curve as i128)); match value.coords { Coordinates::Compressed { x, y } => { - map.insert(Value::Integer(-2), Value::Bytes(x.to_vec())); - map.insert(Value::Integer(-3), Value::Bool(y == ECDSA_Y_PREFIX_NEGATIVE)); + map.insert(-2, Value::Bytes(x.to_vec())); + map.insert(-3, Value::Bool(y == ECDSA_Y_PREFIX_NEGATIVE)); } Coordinates::Uncompressed { x, y } => { - map.insert(Value::Integer(-2), Value::Bytes(x.to_vec())); - map.insert(Value::Integer(-3), Value::Bytes(y.to_vec())); + map.insert(-2, Value::Bytes(x.to_vec())); + map.insert(-3, Value::Bytes(y.to_vec())); } Coordinates::None => { @@ -457,10 +458,10 @@ impl CredentialPublicKey { } CoseKeyInfo::RSA(value) => { - map.insert(Value::Integer(1), Value::Integer(WEBAUTH_PUBLIC_KEY_TYPE_RSA as i128)); - map.insert(Value::Integer(3), Value::Integer(CoseAlgorithmIdentifier::RSA as i128)); - map.insert(Value::Integer(-1), Value::Bytes(value.n)); - map.insert(Value::Integer(-2), Value::Bytes(value.e)); + map.insert(1, Value::Integer(WEBAUTH_PUBLIC_KEY_TYPE_RSA as i128)); + map.insert(3, Value::Integer(self.alg as i128)); + map.insert(-1, Value::Bytes(value.n)); + map.insert(-2, Value::Bytes(value.e)); } }; to_vec(&map).map_err(Error::CborError) @@ -549,10 +550,11 @@ impl Message for AttestationObject { None => Value::Null, }; - let mut att_obj = BTreeMap::new(); - att_obj.insert("authData".to_string(), Value::Bytes(self.auth_data.to_vec()?)); + //Using an index map here because we must preserve ordering + let mut att_obj = IndexMap::new(); att_obj.insert("fmt".to_string(), Value::Text(self.fmt)); att_obj.insert("attStmt".to_string(), att_stmt); + att_obj.insert("authData".to_string(), Value::Bytes(self.auth_data.to_vec()?)); to_vec(&att_obj).map_err(Error::CborError) } @@ -672,7 +674,7 @@ impl FromStr for Coordinates { #[derive(PartialEq, Debug, Default, Serialize, Deserialize, Clone, Copy)] pub enum CoseAlgorithmIdentifier { Ed25519 = -8, - EC2 = -7, + ES256 = -7, RSA = -257, RS1 = -65535, #[default] @@ -684,7 +686,19 @@ impl From for CoseAlgorithmIdentifier { match value { -65535 => CoseAlgorithmIdentifier::RS1, -257 => CoseAlgorithmIdentifier::RSA, - -7 => CoseAlgorithmIdentifier::EC2, + -7 => CoseAlgorithmIdentifier::ES256, + -8 => CoseAlgorithmIdentifier::Ed25519, + _ => CoseAlgorithmIdentifier::NotSupported, + } + } +} + +impl From for CoseAlgorithmIdentifier { + fn from(value: i32) -> Self { + match value { + -65535 => CoseAlgorithmIdentifier::RS1, + -257 => CoseAlgorithmIdentifier::RSA, + -7 => CoseAlgorithmIdentifier::ES256, -8 => CoseAlgorithmIdentifier::Ed25519, _ => CoseAlgorithmIdentifier::NotSupported, } @@ -696,7 +710,7 @@ impl From for i64 { match value { CoseAlgorithmIdentifier::RS1 => -65535, CoseAlgorithmIdentifier::RSA => -257, - CoseAlgorithmIdentifier::EC2 => -7, + CoseAlgorithmIdentifier::ES256 => -7, CoseAlgorithmIdentifier::Ed25519 => -8, _ => -65536, //Unassigned } diff --git a/src/webauthn/proto/tpm.rs b/src/webauthn/proto/tpm.rs index a448204..dfac4dc 100644 --- a/src/webauthn/proto/tpm.rs +++ b/src/webauthn/proto/tpm.rs @@ -152,7 +152,7 @@ impl TPM { return Err(Error::TpmError(TpmError::PublicKeyParametersMismatch(credential_pk.alg))); } } - (CoseAlgorithmIdentifier::EC2, AlgParameters::ECC(params), TpmuPublicId::Ecc(ecc_points)) => { + (CoseAlgorithmIdentifier::ES256, AlgParameters::ECC(params), TpmuPublicId::Ecc(ecc_points)) => { if let CoseKeyInfo::EC2(ec2) = &credential_pk.key_info { if !matches!( (ec2.curve, params.curve_id), diff --git a/src/webauthn/server/mod.rs b/src/webauthn/server/mod.rs index 9c7d222..970d658 100644 --- a/src/webauthn/server/mod.rs +++ b/src/webauthn/server/mod.rs @@ -498,7 +498,7 @@ impl CredentialRequestBuilder { fn get_alg_from_cose(id: i64) -> &'static SignatureAlgorithm { match CoseAlgorithmIdentifier::from(id) { - CoseAlgorithmIdentifier::EC2 => &webpki::ECDSA_P256_SHA256, + CoseAlgorithmIdentifier::ES256 => &webpki::ECDSA_P256_SHA256, CoseAlgorithmIdentifier::RSA => &webpki::RSA_PKCS1_2048_8192_SHA256, _ => &webpki::ECDSA_P256_SHA256, } @@ -693,7 +693,7 @@ fn get_ring_alg_from_cose(id: i64, key_info: &CoseKeyInfo) -> Result<&'static dy "Unsupported algorithm", )))), }, - (CoseAlgorithmIdentifier::EC2, CoseKeyInfo::EC2(ec2)) => match ec2.curve { + (CoseAlgorithmIdentifier::ES256, CoseKeyInfo::EC2(ec2)) => match ec2.curve { ECDSA_CURVE_P256 => Ok(&signature::ECDSA_P256_SHA256_ASN1), ECDSA_CURVE_P384 => Ok(&signature::ECDSA_P384_SHA384_ASN1), _ => Err(Error::CredentialError(CredentialError::Other(String::from( diff --git a/wrappers/swift/build.sh b/wrappers/swift/build.sh new file mode 100755 index 0000000..9713b30 --- /dev/null +++ b/wrappers/swift/build.sh @@ -0,0 +1,4 @@ +cargo lipo --release +mv target/universal/release/libslauth.a target/universal/release/libslauth_universal.a +mv target/x86_64-apple-ios/release/libslauth.a target/x86_64-apple-ios/release/libslauth_x86.a +mv target/aarch64-apple-ios/release/libslauth.a target/aarch64-apple-ios/release/libslauth_arm64.a \ No newline at end of file diff --git a/wrappers/swift/classes/Hotp.swift b/wrappers/swift/classes/Hotp.swift new file mode 100644 index 0000000..966477f --- /dev/null +++ b/wrappers/swift/classes/Hotp.swift @@ -0,0 +1,60 @@ +// +// Hotp.swift +// firebase +// +// Created by Richer Archambault on 2019-04-26. +// Copyright © 2019 Sebastien Aubin. All rights reserved. +// + +import Foundation + +public class Hotp: NSObject, RustObject { + var raw: OpaquePointer + + required init(raw: OpaquePointer) { + self.raw = raw + } + + func intoRaw() -> OpaquePointer { + return self.raw + } + + public convenience init(uri: String) throws { + let r = hotp_from_uri(uri) + if r == nil { + throw Err(message: "InvalidUri") + } else { + self.init(raw: r!) + } + } + + deinit { + hotp_free(raw) + } + + public func to_uri(label: String, issuer: String) -> String { + let uri = hotp_to_uri(raw, label, issuer) + let s = String(cString: uri!) + free(uri) + return s + } + + public func inc() { + hotp_inc(raw) + } + + public func gen() -> String { + let code = hotp_gen(raw) + let s_code = String(cString: code!) + free(code) + return s_code + } + + public func verify(code: String) -> Bool { + return hotp_verify(raw, code) + } + + public func validate_current(code: String) -> Bool { + return hotp_validate_current(raw, code) + } +} diff --git a/wrappers/swift/classes/RustObject.swift b/wrappers/swift/classes/RustObject.swift new file mode 100644 index 0000000..d92dbd8 --- /dev/null +++ b/wrappers/swift/classes/RustObject.swift @@ -0,0 +1,18 @@ +// +// RustObject.swift +// firebase +// +// Created by Richer Archambault on 2019-04-26. +// Copyright © 2019 Sebastien Aubin. All rights reserved. +// + +import Foundation + +protocol RustObject { + init(raw: OpaquePointer) + func intoRaw() -> OpaquePointer +} + +struct Err: Error { + let message: String +} diff --git a/wrappers/swift/classes/Totp.swift b/wrappers/swift/classes/Totp.swift new file mode 100644 index 0000000..e223e91 --- /dev/null +++ b/wrappers/swift/classes/Totp.swift @@ -0,0 +1,63 @@ +// +// Totp.swift +// firebase +// +// Created by Richer Archambault on 2019-04-26. +// Copyright © 2019 Sebastien Aubin. All rights reserved. +// + +import Foundation + +public class Totp: NSObject, RustObject { + var raw: OpaquePointer + + required init(raw: OpaquePointer) { + self.raw = raw + } + + func intoRaw() -> OpaquePointer { + return self.raw + } + + public convenience init(uri: String) throws { + let r = totp_from_uri(uri) + if r == nil { + throw Err(message: "InvalidUri") + } else { + self.init(raw: r!) + } + } + + deinit { + totp_free(raw) + } + + public func to_uri(label: String, issuer: String) -> String { + let uri = totp_to_uri(raw, label, issuer) + let s = String(cString: uri!) + free(uri) + return s + } + + public func gen() -> String { + let code = totp_gen(raw) + let s = String(cString: code!) + free(code) + return s + } + + public func gen_with(elapsed: UInt) -> String { + let code = totp_gen_with(raw, elapsed) + let s = String(cString: code!) + free(code) + return s + } + + public func verify(code: String) -> Bool { + return totp_verify(raw, code) + } + + public func validate_current(code: String) -> Bool { + return totp_validate_current(raw, code) + } +} diff --git a/wrappers/swift/classes/U2f.swift b/wrappers/swift/classes/U2f.swift new file mode 100644 index 0000000..7f2b6c2 --- /dev/null +++ b/wrappers/swift/classes/U2f.swift @@ -0,0 +1,153 @@ +// +// U2f.swift +// Slauth +// +// Created by Richer Archambault on 2019-07-01. +// + +import Foundation + +public class WebRequest : RustObject { + var raw: OpaquePointer + + required init(raw: OpaquePointer) { + self.raw = raw + } + + public convenience init?(json: String) { + let pointer = web_request_from_json(json) + if pointer == nil { + return nil + } + self.init(raw: pointer!) + } + + public func intoRaw() -> OpaquePointer { + return self.raw + } + + deinit { + if raw != nil { + web_request_free(raw) + } + } + + public func isRegister() -> Bool { + return web_request_is_register(raw) + } + + public func isSign() -> Bool { + return web_request_is_sign(raw) + } + + public func getOrigin() -> Optional { + let cOrigin = web_request_origin(raw) + if cOrigin == nil { + return .none + } + + let origin = String(cString: cOrigin!) + free(cOrigin) + return .some(origin) + } + + public func getTimeout() -> UInt64 { + return web_request_timeout(raw) + } + + public func getKeyHandle(origin: String) -> Optional { + if self.isSign() { + let cKeyHandle = web_request_key_handle(raw, origin) + if cKeyHandle == nil { + return .none + } + + let keyHandle = String(cString: cKeyHandle!) + free(cKeyHandle) + return .some(keyHandle) + } else { + return .none + } + } + + public func register(origin: String, attestationCert: [UInt8], attestationKey: [UInt8]) -> WebResponse { + return WebResponse(raw: web_request_register(raw, origin, attestationCert, UInt64(attestationCert.count), attestationKey, UInt64(attestationKey.count))) + } + + public func sign(origin: String, signingKey: SigningKey, counter: UInt32, userPresence: Bool) -> WebResponse { + return WebResponse(raw: web_request_sign(raw, signingKey.intoRaw(), origin, UInt(counter), userPresence)) + } + +} + +public class SigningKey: RustObject { + var raw: OpaquePointer + + public required init(raw: OpaquePointer) { + self.raw = raw + } + + public convenience init?(string: String) { + let pointer = signing_key_from_string(string) + + if pointer == nil { + return nil + } + + self.init(raw: pointer!) + } + + func intoRaw() -> OpaquePointer { + return self.raw + } + + deinit { + signing_key_free(raw) + } + + public func getKeyHandle() -> String { + let cString = signing_key_get_key_handle(raw) + let keyHandle = String(cString: cString!) + free(cString) + return keyHandle + } + + public func toString() -> String { + let csString = signing_key_to_string(raw) + let sign = String(cString: csString!) + free(csString) + return sign + } +} + +public class WebResponse: RustObject { + var raw: OpaquePointer + + public required init(raw: OpaquePointer) { + self.raw = raw + } + + func intoRaw() -> OpaquePointer { + return self.raw + } + + deinit { + client_web_response_free(raw) + } + + public func getSigningKey() -> Optional { + let rawKey = client_web_response_signing_key(raw) + if rawKey == nil { + return .none + } + + return .some(SigningKey(raw: rawKey!)) + } + + public func toJson() -> String { + let cJsonString = client_web_response_to_json(raw) + let json = String(cString: cJsonString!) + free(cJsonString) + return json + } +} diff --git a/wrappers/swift/classes/WebAuthnCreationResponse.swift b/wrappers/swift/classes/WebAuthnCreationResponse.swift new file mode 100644 index 0000000..52f87f5 --- /dev/null +++ b/wrappers/swift/classes/WebAuthnCreationResponse.swift @@ -0,0 +1,64 @@ +import Foundation +import AuthenticationServices + +@available(iOS 15.0, *) +public class WebAuthnCreationResponse: NSObject { + + var raw: OpaquePointer + var aaguid: String + + required init(raw: OpaquePointer, aaguid: String) { + self.raw = raw + self.aaguid = aaguid + } + + func intoRaw() -> OpaquePointer { + return self.raw + } + + public func getPrivateKey() -> String { + let cString = get_private_key_from_response(self.raw) + let privateKey = String(cString: cString!) + free(cString) + return privateKey + } + + public func getAttestationObject() -> Data { + let buffer = get_attestation_object_from_response(self.raw) + return Data(bytes: buffer.data, count: Int(buffer.len)) + } + + public convenience init(aaguid: String, credentialId: Data, rpId: String, attestationFlags: UInt8, cose_algorithm_identifiers: [ASCOSEAlgorithmIdentifier]) throws { + let credentialPointer = UnsafeMutablePointer.allocate(capacity: credentialId.count) + credentialId.copyBytes(to: credentialPointer, count: credentialId.count) + + let cose_algorithm_identifiers_pointer = UnsafeMutablePointer.allocate(capacity: cose_algorithm_identifiers.count) + for i in 0...(cose_algorithm_identifiers.count - 1) { + cose_algorithm_identifiers_pointer[i] = Int32(cose_algorithm_identifiers[i].rawValue) + } + + let r = generate_credential_creation_response(aaguid, credentialPointer, UInt(credentialId.count), rpId, attestationFlags.bigEndian, cose_algorithm_identifiers_pointer, UInt(cose_algorithm_identifiers.count)) + if r == nil { + throw Err(message: "Invalid parameters") + } else { + self.init(raw: r!, aaguid: aaguid) + } + credentialPointer.deallocate() + cose_algorithm_identifiers_pointer.deallocate() + } + + deinit { + response_free(raw) + } +} + +public enum AttestationFlags: UInt8 { + case userPresent = 1 + //Reserved for future = 2 + case userVerified = 4 + case backupEligible = 8 + case backedUp = 16 + //Reserved for future = 32 + case attestedCredentialDataIncluded = 64 + case extensionDataIncluded = 128 +} diff --git a/wrappers/swift/classes/WebAuthnRequestResponse.swift b/wrappers/swift/classes/WebAuthnRequestResponse.swift new file mode 100644 index 0000000..a5c4303 --- /dev/null +++ b/wrappers/swift/classes/WebAuthnRequestResponse.swift @@ -0,0 +1,36 @@ +@available(iOS 15.0, *) +public class WebAuthnRequestResponse: NSObject, RustObject { + + var raw: OpaquePointer + + required init(raw: OpaquePointer) { + self.raw = raw + } + + func intoRaw() -> OpaquePointer { + return self.raw + } + + public func getAuthData() -> Data { + let buffer = get_auth_data_from_response(self.raw) + return Data(bytes: buffer.data, count: Int(buffer.len)) + } + + public func getSignature() -> Data { + let buffer = get_signature_from_response(self.raw) + return Data(bytes: buffer.data, count: Int(buffer.len)) + } + + public convenience init(rpId: String, attestationFlags: UInt8, clientDataHash: Data, privateKey: String) throws { + let clientDataHashPointer = UnsafeMutablePointer.allocate(capacity: clientDataHash.count) + clientDataHash.copyBytes(to: clientDataHashPointer, count: clientDataHash.count) + + let r = generate_credential_request_response(rpId, privateKey, attestationFlags.bigEndian, clientDataHashPointer, UInt(clientDataHash.count)) + if r == nil { + throw Err(message: "Invalid parameters") + } else { + self.init(raw: r!) + } + clientDataHashPointer.deallocate() + } +} \ No newline at end of file diff --git a/wasm/build.sh b/wrappers/wasm/build.sh similarity index 100% rename from wasm/build.sh rename to wrappers/wasm/build.sh diff --git a/wasm/example/otp-example/.editorconfig b/wrappers/wasm/example/otp-example/.editorconfig similarity index 100% rename from wasm/example/otp-example/.editorconfig rename to wrappers/wasm/example/otp-example/.editorconfig diff --git a/wasm/example/otp-example/.gitignore b/wrappers/wasm/example/otp-example/.gitignore similarity index 100% rename from wasm/example/otp-example/.gitignore rename to wrappers/wasm/example/otp-example/.gitignore diff --git a/wasm/example/otp-example/README.md b/wrappers/wasm/example/otp-example/README.md similarity index 100% rename from wasm/example/otp-example/README.md rename to wrappers/wasm/example/otp-example/README.md diff --git a/wasm/example/otp-example/angular.json b/wrappers/wasm/example/otp-example/angular.json similarity index 100% rename from wasm/example/otp-example/angular.json rename to wrappers/wasm/example/otp-example/angular.json diff --git a/wasm/example/otp-example/browserslist b/wrappers/wasm/example/otp-example/browserslist similarity index 100% rename from wasm/example/otp-example/browserslist rename to wrappers/wasm/example/otp-example/browserslist diff --git a/wasm/example/otp-example/e2e/protractor.conf.js b/wrappers/wasm/example/otp-example/e2e/protractor.conf.js similarity index 100% rename from wasm/example/otp-example/e2e/protractor.conf.js rename to wrappers/wasm/example/otp-example/e2e/protractor.conf.js diff --git a/wasm/example/otp-example/e2e/src/app.e2e-spec.ts b/wrappers/wasm/example/otp-example/e2e/src/app.e2e-spec.ts similarity index 100% rename from wasm/example/otp-example/e2e/src/app.e2e-spec.ts rename to wrappers/wasm/example/otp-example/e2e/src/app.e2e-spec.ts diff --git a/wasm/example/otp-example/e2e/src/app.po.ts b/wrappers/wasm/example/otp-example/e2e/src/app.po.ts similarity index 100% rename from wasm/example/otp-example/e2e/src/app.po.ts rename to wrappers/wasm/example/otp-example/e2e/src/app.po.ts diff --git a/wasm/example/otp-example/e2e/tsconfig.json b/wrappers/wasm/example/otp-example/e2e/tsconfig.json similarity index 100% rename from wasm/example/otp-example/e2e/tsconfig.json rename to wrappers/wasm/example/otp-example/e2e/tsconfig.json diff --git a/wasm/example/otp-example/karma.conf.js b/wrappers/wasm/example/otp-example/karma.conf.js similarity index 100% rename from wasm/example/otp-example/karma.conf.js rename to wrappers/wasm/example/otp-example/karma.conf.js diff --git a/wasm/example/otp-example/package-lock.json b/wrappers/wasm/example/otp-example/package-lock.json similarity index 100% rename from wasm/example/otp-example/package-lock.json rename to wrappers/wasm/example/otp-example/package-lock.json diff --git a/wasm/example/otp-example/package.json b/wrappers/wasm/example/otp-example/package.json similarity index 100% rename from wasm/example/otp-example/package.json rename to wrappers/wasm/example/otp-example/package.json diff --git a/wasm/example/otp-example/src/app/app-routing.module.ts b/wrappers/wasm/example/otp-example/src/app/app-routing.module.ts similarity index 100% rename from wasm/example/otp-example/src/app/app-routing.module.ts rename to wrappers/wasm/example/otp-example/src/app/app-routing.module.ts diff --git a/wasm/example/otp-example/src/app/app.component.css b/wrappers/wasm/example/otp-example/src/app/app.component.css similarity index 100% rename from wasm/example/otp-example/src/app/app.component.css rename to wrappers/wasm/example/otp-example/src/app/app.component.css diff --git a/wasm/example/otp-example/src/app/app.component.html b/wrappers/wasm/example/otp-example/src/app/app.component.html similarity index 100% rename from wasm/example/otp-example/src/app/app.component.html rename to wrappers/wasm/example/otp-example/src/app/app.component.html diff --git a/wasm/example/otp-example/src/app/app.component.spec.ts b/wrappers/wasm/example/otp-example/src/app/app.component.spec.ts similarity index 100% rename from wasm/example/otp-example/src/app/app.component.spec.ts rename to wrappers/wasm/example/otp-example/src/app/app.component.spec.ts diff --git a/wasm/example/otp-example/src/app/app.component.ts b/wrappers/wasm/example/otp-example/src/app/app.component.ts similarity index 100% rename from wasm/example/otp-example/src/app/app.component.ts rename to wrappers/wasm/example/otp-example/src/app/app.component.ts diff --git a/wasm/example/otp-example/src/app/app.module.ts b/wrappers/wasm/example/otp-example/src/app/app.module.ts similarity index 100% rename from wasm/example/otp-example/src/app/app.module.ts rename to wrappers/wasm/example/otp-example/src/app/app.module.ts diff --git a/wasm/example/otp-example/src/app/services/otp.service.ts b/wrappers/wasm/example/otp-example/src/app/services/otp.service.ts similarity index 100% rename from wasm/example/otp-example/src/app/services/otp.service.ts rename to wrappers/wasm/example/otp-example/src/app/services/otp.service.ts diff --git a/wasm/example/otp-example/src/assets/.gitkeep b/wrappers/wasm/example/otp-example/src/assets/.gitkeep similarity index 100% rename from wasm/example/otp-example/src/assets/.gitkeep rename to wrappers/wasm/example/otp-example/src/assets/.gitkeep diff --git a/wasm/example/otp-example/src/environments/environment.prod.ts b/wrappers/wasm/example/otp-example/src/environments/environment.prod.ts similarity index 100% rename from wasm/example/otp-example/src/environments/environment.prod.ts rename to wrappers/wasm/example/otp-example/src/environments/environment.prod.ts diff --git a/wasm/example/otp-example/src/environments/environment.ts b/wrappers/wasm/example/otp-example/src/environments/environment.ts similarity index 100% rename from wasm/example/otp-example/src/environments/environment.ts rename to wrappers/wasm/example/otp-example/src/environments/environment.ts diff --git a/wasm/example/otp-example/src/favicon.ico b/wrappers/wasm/example/otp-example/src/favicon.ico similarity index 100% rename from wasm/example/otp-example/src/favicon.ico rename to wrappers/wasm/example/otp-example/src/favicon.ico diff --git a/wasm/example/otp-example/src/index.html b/wrappers/wasm/example/otp-example/src/index.html similarity index 100% rename from wasm/example/otp-example/src/index.html rename to wrappers/wasm/example/otp-example/src/index.html diff --git a/wasm/example/otp-example/src/main.ts b/wrappers/wasm/example/otp-example/src/main.ts similarity index 100% rename from wasm/example/otp-example/src/main.ts rename to wrappers/wasm/example/otp-example/src/main.ts diff --git a/wasm/example/otp-example/src/polyfills.ts b/wrappers/wasm/example/otp-example/src/polyfills.ts similarity index 100% rename from wasm/example/otp-example/src/polyfills.ts rename to wrappers/wasm/example/otp-example/src/polyfills.ts diff --git a/wasm/example/otp-example/src/styles.css b/wrappers/wasm/example/otp-example/src/styles.css similarity index 100% rename from wasm/example/otp-example/src/styles.css rename to wrappers/wasm/example/otp-example/src/styles.css diff --git a/wasm/example/otp-example/src/test.ts b/wrappers/wasm/example/otp-example/src/test.ts similarity index 100% rename from wasm/example/otp-example/src/test.ts rename to wrappers/wasm/example/otp-example/src/test.ts diff --git a/wasm/example/otp-example/tsconfig.app.json b/wrappers/wasm/example/otp-example/tsconfig.app.json similarity index 100% rename from wasm/example/otp-example/tsconfig.app.json rename to wrappers/wasm/example/otp-example/tsconfig.app.json diff --git a/wasm/example/otp-example/tsconfig.json b/wrappers/wasm/example/otp-example/tsconfig.json similarity index 100% rename from wasm/example/otp-example/tsconfig.json rename to wrappers/wasm/example/otp-example/tsconfig.json diff --git a/wasm/example/otp-example/tsconfig.spec.json b/wrappers/wasm/example/otp-example/tsconfig.spec.json similarity index 100% rename from wasm/example/otp-example/tsconfig.spec.json rename to wrappers/wasm/example/otp-example/tsconfig.spec.json diff --git a/wasm/example/otp-example/tslint.json b/wrappers/wasm/example/otp-example/tslint.json similarity index 100% rename from wasm/example/otp-example/tslint.json rename to wrappers/wasm/example/otp-example/tslint.json