mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-23 09:56:23 +03:00
Extract more data from the URI
This commit is contained in:
parent
5550ebd7d3
commit
445034151a
@ -18,7 +18,6 @@ import 'dart:typed_data';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:base32/base32.dart';
|
import 'package:base32/base32.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:convert/convert.dart';
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:yubico_authenticator/app/logging.dart';
|
import 'package:yubico_authenticator/app/logging.dart';
|
||||||
@ -128,7 +127,17 @@ class CredentialData with _$CredentialData {
|
|||||||
factory CredentialData.fromJson(Map<String, dynamic> json) =>
|
factory CredentialData.fromJson(Map<String, dynamic> json) =>
|
||||||
_$CredentialDataFromJson(json);
|
_$CredentialDataFromJson(json);
|
||||||
|
|
||||||
factory CredentialData.fromUri(Uri uri) {
|
static List<CredentialData> multiFromUri(uri) {
|
||||||
|
if (uri.scheme.toLowerCase() == 'otpauth-migration') {
|
||||||
|
return CredentialData.fromMigration(uri);
|
||||||
|
} else if (uri.scheme.toLowerCase() == 'otpauth') {
|
||||||
|
return [CredentialData.fromUri(uri)];
|
||||||
|
} else {
|
||||||
|
throw ArgumentError('Invalid scheme');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<CredentialData> fromMigration(uri) {
|
||||||
List<dynamic> read(Uint8List bytes) {
|
List<dynamic> read(Uint8List bytes) {
|
||||||
final index = bytes[0];
|
final index = bytes[0];
|
||||||
final sublist1 = bytes.sublist(1, index + 1);
|
final sublist1 = bytes.sublist(1, index + 1);
|
||||||
@ -141,85 +150,115 @@ class CredentialData with _$CredentialData {
|
|||||||
return utf8.decode(encodedData.runes.toList());
|
return utf8.decode(encodedData.runes.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uri.scheme.toLowerCase() == 'otpauth-migration') {
|
final uriString = uri.toString();
|
||||||
final uriString = uri.toString();
|
var data = Uint8List.fromList(
|
||||||
var data = Uint8List.fromList(
|
base64.decode(Uri.decodeComponent(uriString.split('=')[1])));
|
||||||
base64.decode(Uri.decodeComponent(uriString.split('=')[1])));
|
|
||||||
|
|
||||||
var credentials = <CredentialData>[];
|
var credentials = <CredentialData>[];
|
||||||
|
|
||||||
var tag = data[0];
|
var tag = data[0];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Assuming the credential(s) follow the format:
|
Assuming the credential(s) follow the format:
|
||||||
cred = 0aLENGTH0aSECRET12NAME1aISSUER200128013002xxx
|
cred = 0aLENGTH0aSECRET12NAME1aISSUER20ALGO28DIGITS30OATHxxx
|
||||||
where xxx can be another cred.
|
where xxx can be another cred.
|
||||||
*/
|
*/
|
||||||
while (tag == 10) {
|
while (tag == 10) {
|
||||||
// 0a tag means new credential.
|
// 0a tag means new credential.
|
||||||
|
|
||||||
//var length = data[1]; // The length of this credential
|
// Extract secret, name, and issuer
|
||||||
var secretTag = data[2];
|
var secretTag = data[2];
|
||||||
if (secretTag != 10) {
|
if (secretTag != 10) {
|
||||||
// tag before secret is 0a hex
|
// tag before secret is 0a hex
|
||||||
throw ArgumentError('Invalid scheme, no secret tag');
|
throw ArgumentError('Invalid scheme, no secret tag');
|
||||||
}
|
}
|
||||||
data = data.sublist(3);
|
data = data.sublist(3);
|
||||||
final result1 = read(data);
|
final result1 = read(data);
|
||||||
final secret = result1[0];
|
final secret = result1[0];
|
||||||
data = result1[1];
|
data = result1[1];
|
||||||
final decodedSecret = b32Encode(secret);
|
final decodedSecret = b32Encode(secret);
|
||||||
|
|
||||||
var nameTag = data[0];
|
var nameTag = data[0];
|
||||||
if (nameTag != 18) {
|
if (nameTag != 18) {
|
||||||
// tag before name is 12 hex
|
// tag before name is 12 hex
|
||||||
throw ArgumentError('Invalid scheme, no name tag');
|
throw ArgumentError('Invalid scheme, no name tag');
|
||||||
}
|
}
|
||||||
|
data = data.sublist(1);
|
||||||
|
final result2 = read(data);
|
||||||
|
final name = result2[0];
|
||||||
|
data = result2[1];
|
||||||
|
|
||||||
|
var issuerTag = data[0];
|
||||||
|
List<int>? issuer;
|
||||||
|
|
||||||
|
if (issuerTag == 26) {
|
||||||
|
// tag before issuer is 1a hex, but issuer is optional.
|
||||||
data = data.sublist(1);
|
data = data.sublist(1);
|
||||||
final result2 = read(data);
|
final result3 = read(data);
|
||||||
final name = result2[0];
|
issuer = result3[0];
|
||||||
data = result2[1];
|
data = result3[1];
|
||||||
|
}
|
||||||
|
|
||||||
var issuerTag = data[0];
|
// Extract algorithm, number of digits, and oath type:
|
||||||
List<int>? issuer;
|
var algoTag = data[0];
|
||||||
|
if (algoTag != 32) {
|
||||||
|
// tag before algo is 20 hex
|
||||||
|
throw ArgumentError('Invalid scheme, no algo tag');
|
||||||
|
}
|
||||||
|
int algo = data[1];
|
||||||
|
|
||||||
if (issuerTag == 26) {
|
var digitsTag = data[2];
|
||||||
// tag before issuer is 1a hex, but issuer is optional.
|
if (digitsTag != 40) {
|
||||||
data = data.sublist(1);
|
// tag before digits is 28 hex
|
||||||
final result3 = read(data);
|
throw ArgumentError('Invalid scheme, no digits tag');
|
||||||
issuer = result3[0];
|
}
|
||||||
data = result3[1];
|
var digits = data[3];
|
||||||
}
|
|
||||||
final credential = CredentialData(
|
|
||||||
issuer: issuerTag != 26
|
|
||||||
? null
|
|
||||||
: utf8.decode(issuer!, allowMalformed: true),
|
|
||||||
name: utf8.decode(name, allowMalformed: true),
|
|
||||||
secret: decodedSecret,
|
|
||||||
);
|
|
||||||
|
|
||||||
credentials.add(credential);
|
var oathTag = data[4];
|
||||||
|
if (oathTag != 48) {
|
||||||
|
// tag before oath is 30 hex
|
||||||
|
throw ArgumentError('Invalid scheme, no oath tag');
|
||||||
|
}
|
||||||
|
var oathType = data[5];
|
||||||
|
|
||||||
var endTag = data.sublist(0, 6);
|
int counter = defaultCounter;
|
||||||
if (hex.encode(endTag) != '200128013002') {
|
if (oathType == 1) {
|
||||||
// At the end of every credential there is 200128013002
|
// if hotp, extract counter
|
||||||
throw ArgumentError('Invalid scheme, no end tag');
|
counter = data[7];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final credential = CredentialData(
|
||||||
|
issuer:
|
||||||
|
issuerTag != 26 ? null : utf8.decode(issuer!, allowMalformed: true),
|
||||||
|
name: utf8.decode(name, allowMalformed: true),
|
||||||
|
oathType: oathType == 1 ? OathType.hotp : OathType.totp,
|
||||||
|
secret: decodedSecret,
|
||||||
|
hashAlgorithm: algo == 1
|
||||||
|
? HashAlgorithm.sha1
|
||||||
|
: (algo == 2 ? HashAlgorithm.sha256 : HashAlgorithm.sha512),
|
||||||
|
digits: digits == 1 ? defaultDigits : 8,
|
||||||
|
counter: counter,
|
||||||
|
);
|
||||||
|
|
||||||
|
credentials.add(credential);
|
||||||
|
if (oathType == 1) {
|
||||||
|
data = data.sublist(8);
|
||||||
|
} else {
|
||||||
data = data.sublist(6);
|
data = data.sublist(6);
|
||||||
tag = data[0];
|
|
||||||
}
|
}
|
||||||
|
tag = data[0];
|
||||||
// Print all the extracted credentials
|
|
||||||
for (var credential in credentials) {
|
|
||||||
_log.debug(
|
|
||||||
'${credential.issuer} (${credential.name}) ${credential.secret}');
|
|
||||||
}
|
|
||||||
|
|
||||||
return credentials[0]; // For now, return only the first credential.
|
|
||||||
} else if (uri.scheme.toLowerCase() != 'otpauth') {
|
|
||||||
throw ArgumentError('Invalid scheme, must be "otpauth://"');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Print all the extracted credentials
|
||||||
|
for (var credential in credentials) {
|
||||||
|
_log.debug(
|
||||||
|
'${credential.issuer} (${credential.name}) ${credential.secret}');
|
||||||
|
}
|
||||||
|
|
||||||
|
return credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory CredentialData.fromUri(Uri uri) {
|
||||||
final oathType = OathType.values.byName(uri.host.toLowerCase());
|
final oathType = OathType.values.byName(uri.host.toLowerCase());
|
||||||
final params = uri.queryParameters;
|
final params = uri.queryParameters;
|
||||||
String? issuer;
|
String? issuer;
|
||||||
|
@ -130,8 +130,8 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
|||||||
_qrState = _QrScanState.failed;
|
_qrState = _QrScanState.failed;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
final data = CredentialData.fromUri(Uri.parse(otpauth));
|
final data = CredentialData.multiFromUri(Uri.parse(otpauth));
|
||||||
_loadCredentialData(data);
|
_loadCredentialData(data[0]); // TODO
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
final String errorMessage;
|
final String errorMessage;
|
||||||
|
Loading…
Reference in New Issue
Block a user