airgap-it / airgap-vault

The AirGap Vault is installed on a spare smartphone that has no connection to any network, thus it is air gapped. This app handles the private key.
MIT License
386 stars 109 forks source link

Please make v3.9.0 reproducible #91

Closed Giszmo closed 2 years ago

Giszmo commented 3 years ago

I received version 3.9.0 from the Play Store but can't find the according tag here in the source code.

Also the most likely candidate for the correct revision 5f7ace136ae9377128809155180aa160ecb01ed1 results in a non-trivial diff:

$ ./test.sh --apk /path/to/AirGap\ Vault\ 3.9.0\ \(it.airgap.vault\).apk --revision-override master
...
Results:
appId:          it.airgap.vault
signer:         486381324d8669c80ca9b8c79d383dc972ec284227d65ebfe9e31cad5fd3f342
apkVersionName: 3.9.0
apkVersionCode: 33523
verdict:        
appHash:        62d2c09cb1cbceeab1b90cf19e94e7e1c2385cf1bf852629200dea0999ea1c62
commit:         5f7ace136ae9377128809155180aa160ecb01ed1

Diff:
Files /tmp/fromPlay_it.airgap.vault_33523/assets/public/3rdpartylicenses.txt and /tmp/fromBuild_it.airgap.vault_33523/assets/public/3rdpartylicenses.txt differ
Files /tmp/fromPlay_it.airgap.vault_33523/assets/public/index.html and /tmp/fromBuild_it.airgap.vault_33523/assets/public/index.html differ
Only in /tmp/fromPlay_it.airgap.vault_33523/assets/public: main.1991060c36f34c10328b.js
Only in /tmp/fromBuild_it.airgap.vault_33523/assets/public: main.462813af45843585aca4.js
Files /tmp/fromPlay_it.airgap.vault_33523/META-INF/MANIFEST.MF and /tmp/fromBuild_it.airgap.vault_33523/META-INF/MANIFEST.MF differ
Only in /tmp/fromPlay_it.airgap.vault_33523/META-INF: PAPERS.RSA
Only in /tmp/fromPlay_it.airgap.vault_33523/META-INF: PAPERS.SF

Revision, tag (and its signature):

Full review on WalletScrutiny.

Originalimoc commented 3 years ago

Yes, what happens to that js file🤔🤔🤔

AndreasGassmann commented 3 years ago

We are looking into this.

Do you happen to have access to the files that differ (3rdpartylicenses.txt and index.html files, as well as the js files), so I can compare them locally?

Is it possible to pull the "reproduction attempt" apk from your backend?

Giszmo commented 3 years ago

Do you happen to have access to the files that differ (3rdpartylicenses.txt and index.html files, as well as the js files), so I can compare them locally?

I would have had the built apk some days ago. Will see if this issue is reproducible and provide the files.

Is it possible to pull the "reproduction attempt" apk from your backend?

The apk from Google Play? I intend to make it downloadable but so far it's not.

Giszmo commented 3 years ago

The built license file has these extra lines:


hash.js
MIT

hmac-drbg
MIT

minimalistic-crypto-utils
MIT

Index.html differs in the reference to the different js file:

  <script src="runtime.79b6a44cb7645dc4d93e.js" defer></script><script src="polyfills-es5.8a7ace1d3a29b6ac83a8.js" nomodule defer></script><script src="polyfills.806eec13c09d8ffd7baa.js" defer></script><script src="main.462813af45843585aca4.js" defer></script></body>

The two main.*.js files are 9MB and beautified 13MB. Diffing those beautified JS files, the Google version has an extra block of code around line 1300:

        "+fYP": function(Qt, Ft, Jt) {
            "use strict";
            var Ht = Jt("AZQW"),
                qt = Ht.assert,
                jt = Ht.parseBytes,
                Zt = Ht.cachedProperty;

            function KeyPair(Qt, Ft) {
                this.eddsa = Qt, this._secret = jt(Ft.secret), Qt.isPoint(Ft.pub) ? this._pub = Ft.pub : this._pubBytes = jt(Ft.pub)
            }
            KeyPair.fromPublic = function fromPublic(Qt, Ft) {
                return Ft instanceof KeyPair ? Ft : new KeyPair(Qt, {
                    pub: Ft
                })
            }, KeyPair.fromSecret = function fromSecret(Qt, Ft) {
                return Ft instanceof KeyPair ? Ft : new KeyPair(Qt, {
                    secret: Ft
                })
            }, KeyPair.prototype.secret = function secret() {
                return this._secret
            }, Zt(KeyPair, "pubBytes", function pubBytes() {
                return this.eddsa.encodePoint(this.pub())
            }), Zt(KeyPair, "pub", function pub() {
                return this._pubBytes ? this.eddsa.decodePoint(this._pubBytes) : this.eddsa.g.mul(this.priv())
            }), Zt(KeyPair, "privBytes", function privBytes() {
                var Qt = this.eddsa,
                    Ft = this.hash(),
                    Jt = Qt.encodingLength - 1,
                    Ht = Ft.slice(0, Qt.encodingLength);
                return Ht[0] &= 248, Ht[Jt] &= 127, Ht[Jt] |= 64, Ht
            }), Zt(KeyPair, "priv", function priv() {
                return this.eddsa.decodeInt(this.privBytes())
            }), Zt(KeyPair, "hash", function hash() {
                return this.eddsa.hash().update(this.secret()).digest()
            }), Zt(KeyPair, "messagePrefix", function messagePrefix() {
                return this.hash().slice(this.eddsa.encodingLength)
            }), KeyPair.prototype.sign = function sign(Qt) {
                return qt(this._secret, "KeyPair can only verify"), this.eddsa.sign(Qt, this)
            }, KeyPair.prototype.verify = function verify(Qt, Ft) {
                return this.eddsa.verify(Qt, Ft, this)
            }, KeyPair.prototype.getSecret = function getSecret(Qt) {
                return qt(this._secret, "KeyPair is public only"), Ht.encode(this.secret(), Qt)
            }, KeyPair.prototype.getPublic = function getPublic(Qt) {
                return Ht.encode(this.pubBytes(), Qt)
            }, Qt.exports = KeyPair
        },

and there is many more diffs. It's clearly not just some timestamp.

AndreasGassmann commented 3 years ago

Last time we had this issue the code blocks were actually just sorted differently, but the code inside the codeblocks was the same.

My assumption is that somehow the dependency versions were not the same, which resulted in the difference in the 3rdpartylicense.txt file, as well as the js file. The name of the js file is deterministic depending on the content, so that difference is expected in this case.

I remember reading that in npm 7 and the new package-lock.json format there were some improvements specifically regarding reproducible builds. We will now prepare a new release with those updates, which will then hopefully be reproducible again.

Going forward, we would like to have the check for reproducibility in our release pipeline so this won't happen again.

Is it possible to pull the "reproduction attempt" apk from your backend?

The apk from Google Play? I intend to make it downloadable but so far it's not.

I guess both apks would be helpful. In this case I would like to have your "reproduced" apk, I already have the apk file that we uploaded to google play (which is also on our github).

Originalimoc commented 3 years ago

@Giszmo There's (almost) no way to compromise keys using a permanent offline, QR code communicate device right? Almost 🌚

Giszmo commented 3 years ago

I dropped an apk here. Please grab it soon as it will get deleted soon.

AndreasGassmann commented 3 years ago

Thanks, I downloaded it.

AndreasGassmann commented 2 years ago

3.10.0 has been released, which is reproducible again.