Bubka / 2FAuth

A Web app to manage your Two-Factor Authentication (2FA) accounts and generate their security codes
https://docs.2fauth.app/
GNU Affero General Public License v3.0
2.03k stars 136 forks source link

Google Authenticator - Transfer/Export accounts #74

Closed aronmal closed 2 years ago

aronmal commented 2 years ago

Is your feature request related to a problem? Please describe. When transferring/exporting TOTPs from Google Authenticator, the QR code is not being recognised. This is due to Google Authenticator exporting format is encoded and can be decoded with a simple python script or this migration site which is also open source and generates a QR code to scan afterwards.

Describe the solution you'd like An integrated converter when recognising otpauth-migration://offline?data=... pattern from the QR code and import the profile automatically.

Describe alternatives you've considered Using the simplified python script and manually add the TOTP to the 2fauth app or use the site and scan the resulting QR codes.

Additional context (scanned from Google Authenticator and then hold my fingers in front of the camera)

Bubka commented 2 years ago

FYI there is a discussion about this feature here: https://github.com/Bubka/2FAuth/discussions/27

aronmal commented 2 years ago

Great to see it is already in Mind. But the last comments were made in Aug 2021. I would love to try to contribute at this point maybe, but please brief me real quick, the QR code is being recognized in a picture, the payload is read, in this case protobuf need to be decoded and then the new TOTP is being saved. Could you give me some sort of flow chart what library/methods you use for what step and if they're client or server side. Then I would give myself a try on this.

aronmal commented 2 years ago

Ok, I am currently working on it. I got a big confusion out of my way, the migration format uses Percent-encoding. After that, it is decodeable with protobuf.

Question to you @Bubka, should the conversion from otpauth-migration://offline?data=... to otpauth://totp/... format happen client or server side?

Bubka commented 2 years ago

Question to you @Bubka, should the conversion from otpauth-migration://offline?data=... to otpauth://totp/... format happen client or server side?

Is this just a string conversion or is there some logic to implement?

aronmal commented 2 years ago

Is this just a string conversion or is there some logic to implement?

There is quite some logic with bit shifting and stuff. I am orienting on protobuf-decoder and making progress understanding it. But because he already wrote some of the js conversion, my guess would be to take his approach and adjust it for the otpauth-migration://offline?data=... to otpauth://totp/... format conversion, because why reinvent the wheel. So, my guess would be either to create a npm package to convert the format (which I would love to, but have never done before), or just implement it in your vue frontend at least for now.

What do you think @Bubka ?

aronmal commented 2 years ago

I am blind, there is an official package. I will make a sample script, then you can implement it at the right place in your vue fronted.

aronmal commented 2 years ago

The package is not needed. The .proto file is being compiled to a _pb.js file which than can be used to deserialize the base64 string.

The working code looks like this:

carbon

const Base32 = require('hi-base32'); // Install with 'npm install hi-base32'
const otpauth_migration = require('./otpauth-migration_pb'); // Is compiled with the command (works on linux) 'protoc --js_out=import_style=commonjs,binary:. ./otpauth-migration.proto' and the .proto file from 'https://github.com/digitalduke/otpauth-migration-decoder/blob/master/src/otpauth-migration.proto'

const otpauth_migration_format = 'otpauth-migration://offline?data='; // This is what needs to be replaced from the input with an empty string
const Algorithm = {
    1: 'SHA1',
    2: 'SHA256',
    3: 'SHA512',
    4: 'MD5',
};
const DigitCount = {
    1: '6',
    2: '8',
};
const OtpType = {
    1: 'hotp',
    2: 'totp',
};

const input = "otpauth-migration://offline?data=CjEKCkhlbGxvId6tvu8SGEV4YW1wbGU6YWxpY2VAZ29vZ2xlLmNvbRoHRXhhbXBsZTAC"; // The Input. Obviously.

const uriDecoded = decodeURIComponent(input.replace(otpauth_migration_format, '')); // Replacing function to get only the data and use decodeURIComponent() to decode Percent-encoding. We are left with an base64 string.
const protobufDecoded = otpauth_migration.Payload.deserializeBinary(uriDecoded).array[0]; // Here happens the real magic, the base64 string to an array of OTPs.
const otpDecode = (otpArray) => otpArray.map(otp => { // Map the OTPs array
    const otp_type = OtpType[otp[5] || 1],
        otp_name = otp[1],
        otpParams = {
            Algorithm: Algorithm[otp[3] || 1],
            DigitCount: DigitCount[otp[4] || 1],
            Issuer: otp[2],
            Period: 30,
            Secret: Base32.encode(Buffer.from(otp[0]))
        },
        otp_params = [
            `algorithm=${otpParams.Algorithm}`,
            `digits=${otpParams.DigitCount}`,
            `issuer=${otpParams.Issuer}`,
            `period=${otpParams.Period}`,
            `secret=${otpParams.Secret}`
        ].join('&'),
        otpauth = `otpauth://${otp_type}/${otp_name}?${otp_params}`; //Put together the otpauth link
    return otpauth;
})

console.log(otpDecode(protobufDecoded)); // Évoilà, there we have our exportet/transfered Google Authenticator OTPs
// Expected output: ['otpauth://totp/Example:alice@google.com?algorithm=SHA1&digits=6&issuer=Example&period=30&secret=JBSWY3DPEHPK3PXP']
aronmal commented 2 years ago

https://github.com/aronmal/2FAuth/commit/b61cf94f8080e80f1ea599fa83c6ddb02ff9f4bf in case you need the .proto file or want to test it.

aronmal commented 2 years ago

@Bubka 👀

Bubka commented 2 years ago

Sorry it's a very busy week. I've prioritize the proxy Auth issues since Monday, I hope I could review this on Thursday.

aronmal commented 2 years ago

In this file you have a regex. I don't know much about php, so no plan how to disable this check, but because I'm familiar with typescript and react I found where (here and here) to implement the conversion in vue. So my request would be to not check with the regex, but just parse the decoded QR Code and then do a check in vue if it matches the regex, otherwise 'optauth-migration' regex, in that case convert it or then throw an error.

aronmal commented 2 years ago

And another question, you can export multiple accounts at the same time. You have an array,, but I would just hard code it to take the first in the array.

Bubka commented 2 years ago

Hi @aronmal

I've been reading and testing around protobuf and an import feature past days. And at the end I think that the protobuf decoding has to be done by the back-end.

The main reason is that 2FAuth has all its business logic implemented server-side, and exposes an API through the back-end capabilities. If the protobuf decoding is done by Vue then the API won't be able to provide an endpoint to import GoogleAuth data.

Thanks to your previous contributions I was able to implement the protobuf decoding with PHP 👍🏻

aronmal commented 2 years ago

OK, my Idea was like:

const { data } = await this.form.upload('/api/v1/qrcode/decode', imgdata)

const otpauthRegex = /^otpauth:\/\/[h,t]otp\//i;
const decodedUri = otpauthRegex.test(data.data) ? data.data : otpmigrationDecode(data.data)

this.$router.push({ name: 'createAccount', params: { decodedUri } });

in the Start.vue and Create.vue. But when you can implement it in php it is also fine I guess.

Bubka commented 2 years ago

Sure, but it's not that simple.

2Fauth uses 2 different QR decoders, one for the live scan, run by Vue, and one for uploaded QRs, run by the back-end.

Moreover, forcing the user to import a single account whereas Google Authenticator allows to export the whole accounts would result in a bad UX. It is also necessary to create a dedicated UI to handle specific messages during the import process, especially in case of treatment error.

I'm working on it.

Bubka commented 2 years ago

FYI, I committed some changes to the dev branch this afternoon, Google Authenticator export is now supported, just scan/upload the QR code provided by G-Auth to fire the import feature.

The 2fauth/2fauth:dev docker image is up-to-date if you want to give it a try.

aronmal commented 2 years ago

I will give it a try when I find time for it. Nice job!