Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Increase the size of the webauthn_credential credential_id field (#18739) #18756

Merged
merged 5 commits into from
Feb 14, 2022

Conversation

zeripath
Copy link
Contributor

@zeripath zeripath commented Feb 13, 2022

Backport #18739
Backport #18760

Unfortunately credentialIDs in u2f are 255 bytes long which with base32 encoding
becomes 408 bytes. The default size of a xorm string field is only a VARCHAR(255)

This problem is not apparent on SQLite because strings get mapped to TEXT there.

Fix #18727

Signed-off-by: Andrew Thornton [email protected]

…gitea#18739)

Backport go-gitea#18739

Unfortunately credentialIDs in u2f are 255 bytes long which with base32 encoding
becomes 408 bytes. The default size of a xorm string field is only a VARCHAR(255)

This problem is not apparent on SQLite because strings get mapped to TEXT there.

Fix go-gitea#18727

Signed-off-by: Andrew Thornton <[email protected]>
@zeripath zeripath added this to the 1.16.2 milestone Feb 13, 2022
@lunny

This comment was marked as outdated.

@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Feb 14, 2022
@zeripath

This comment was marked as outdated.

@zeripath
Copy link
Contributor Author

Contains backport for #18760

@GiteaBot GiteaBot added lgtm/need 1 This PR needs approval from one additional maintainer to be merged. and removed lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. labels Feb 14, 2022
@GiteaBot GiteaBot added lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. and removed lgtm/need 1 This PR needs approval from one additional maintainer to be merged. labels Feb 14, 2022
@zeripath
Copy link
Contributor Author

make lgtm work

@zeripath zeripath merged commit 3a78ac4 into go-gitea:release/v1.16 Feb 14, 2022
@zeripath zeripath deleted the backport-18739-v1.16 branch February 14, 2022 21:08
@ashimokawa
Copy link
Contributor

Still does not work

2022/02/14 22:19:29 ...ations/migrations.go:467:Migrate() [I] Migration[207]: Add webauthn table and migrate u2f data to webauthn
2022/02/14 22:19:30 ...ations/migrations.go:467:Migrate() [I] Migration[208]: Use base32.HexEncoding instead of base64 encoding for cred ID as it is case insensitive
2022/02/14 22:19:30 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column name but struct has not related field
2022/02/14 22:19:30 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column lower_name but struct has not related field
2022/02/14 22:19:30 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column user_id but struct has not related field
2022/02/14 22:19:30 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column public_key but struct has not related field
2022/02/14 22:19:30 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column attestation_type but struct has not related field
2022/02/14 22:19:30 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column aaguid but struct has not related field
2022/02/14 22:19:30 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column sign_count but struct has not related field
2022/02/14 22:19:30 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column clone_warning but struct has not related field
2022/02/14 22:19:30 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column created_unix but struct has not related field
2022/02/14 22:19:30 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column updated_unix but struct has not related field
2022/02/14 22:19:30 routers/common/db.go:33:InitDBEngine() [E] ORM engine initialization attempt #1/10 failed. Error: migrate: migration[208]: Use base32.HexEncoding instead of base64 encoding for cred ID as it is case insensitive failed: Error 1406: Data too long for column 'credential_id' at row 1

I guess it is now base32 and 410 bytes are not enough again...

@ashimokawa
Copy link
Contributor

@zeripath

Sorry, I tested probably while you pressed the merge button

@zeripath
Copy link
Contributor Author

The U2F documentation says that the keyHandle should be at most 255 bytes.

https://neowave.fr/pdfs/FIDO-U2F-CHEAT-SHEET.pdf

keyHandle: unique Key index [up to 255 bytes], generated by U2F device, can be purely random or used to wrap private keys related information (can be unsafe)

Base32 represents a 5 bits as 1 byte.

255 * 8 / 5 = 408.

a) ensure that your table is varchar(410) and that you're not rerunning on a previously partially migrated db
b) You need to tell me how long you need this field. I'm not going to make any further guesses.

@ashimokawa
Copy link
Contributor

@zeripath

You are right 410 should be enough when converting 255 bytes into base32, sorry.

But.

a) I did this on a clean gitea 1.15 database, no partially migrated db

b) The migration is broken and truncates data, if gitea depends on truncation (relaxed sql mode that allows it), this should be documented. But I think this is dangerous and should be handled at a layer above the database system itself.

In the end I have to look at the code and see what is being migrated exactly - and what exceeds 255 bytes so that converting to base32 exceeds 410 bytes.

@zeripath
Copy link
Contributor Author

In case you're not aware I am trying to change this so it doesn't truncate data.

@zeripath
Copy link
Contributor Author

zeripath commented Feb 14, 2022

changing v208 to

// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package migrations

import (
	"encoding/base32"
	"encoding/base64"
	"fmt"

	"xorm.io/xorm"
)

func useBase32HexForCredIDInWebAuthnCredential(x *xorm.Engine) error {
	// Create webauthnCredential table
	type webauthnCredential struct {
		ID           int64  `xorm:"pk autoincr"`
		CredentialID string `xorm:"INDEX VARCHAR(410)"`
	}
	if err := x.Sync2(&webauthnCredential{}); err != nil {
		return err
	}

	var start int
	maxLength := 0
	maxLengthID := int64(0)
	regs := make([]*webauthnCredential, 0, 50)
	for {
		err := x.OrderBy("id").Limit(50, start).Find(&regs)
		if err != nil {
			return err
		}

		for _, reg := range regs {
			credID, _ := base64.RawStdEncoding.DecodeString(reg.CredentialID)
			reg.CredentialID = base32.HexEncoding.EncodeToString(credID)
			if maxLength < len(reg.CredentialID) {
				maxLength = len(reg.CredentialID)
				maxLengthID = reg.ID
			}
		}

		if len(regs) < 50 {
			break
		}
		start += 50
		regs = regs[:0]
	}

	fmt.Printf("MaxLength: %d on ID: %d\n", maxLength, maxLengthID)

	return fmt.Errorf("MaxLength: %d on ID: %d\n", maxLength, maxLengthID)
}

@ashimokawa
Copy link
Contributor

@zeripath

I am not sure if this helps, but it seems that the migration happens from the raw blob column. Those have varying sizes from 472 to 1234 bytes.

(I typed this before your last comment, this is not meant to be a reply)

MariaDB [gitea]> select min(octet_length(raw)),max(octet_length(raw)) from u2f_registration;
+------------------------+------------------------+
| min(octet_length(raw)) | max(octet_length(raw)) |
+------------------------+------------------------+
|                    473 |                   1234 |
+------------------------+------------------------+

@ashimokawa
Copy link
Contributor

migration[208]: Use base32.HexEncoding instead of base64 encoding for cred ID as it is case insensitive failed: MaxLength: 408 on ID: 486

So v207 works (base64) but migrating to base32 fails. I double checked that the column is varchar(410)

@zeripath
Copy link
Contributor Author

the maxlength there is 408 chars not 410

how can 410 not be long enough?!

@zeripath
Copy link
Contributor Author

I bet the table is actually being sync'd at 255 because I forgot to update models/auth/webauthn.go too.

@zeripath
Copy link
Contributor Author

Could you update models/auth/webauthn.go:46 to:

	CredentialID    string `xorm:"INDEX VARCHAR(410)"`

@zeripath
Copy link
Contributor Author

(and restore v208.go)

@ashimokawa
Copy link
Contributor

I can try but this looked correct to me:

MariaDB [gitea]> describe webauthn_credential;
+------------------+--------------+------+-----+---------+----------------+
| Field            | Type         | Null | Key | Default | Extra          |
+------------------+--------------+------+-----+---------+----------------+
| id               | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| name             | varchar(255) | YES  |     | NULL    |                |
| lower_name       | varchar(255) | YES  |     | NULL    |                |
| user_id          | bigint(20)   | YES  |     | NULL    |                |
| credential_id    | varchar(410) | YES  | MUL | NULL    |                |
| public_key       | blob         | YES  |     | NULL    |                |
| attestation_type | varchar(255) | YES  |     | NULL    |                |
| aaguid           | blob         | YES  |     | NULL    |                |
| sign_count       | bigint(20)   | YES  |     | NULL    |                |
| clone_warning    | tinyint(1)   | YES  |     | NULL    |                |
| created_unix     | bigint(20)   | YES  |     | NULL    |                |
| updated_unix     | bigint(20)   | YES  |     | NULL    |                |
+------------------+--------------+------+-----+---------+----------------+

@zeripath
Copy link
Contributor Author

I just don't understand why else the db would be moaning about a string being too large when it's smaller than the size of the column. I mean 408 is clearly smaller than 410 (#18756 (comment))

@ashimokawa
Copy link
Contributor

Me neither, ran a test but still

ORM engine initialization attempt #1/10 failed. Error: migrate: migration[208]: Use base32.HexEncoding instead of base64 encoding for cred ID as it is case insensitive failed: Error 1406: Data too long for column 'credential_id' at row 1
``

@zeripath
Copy link
Contributor Author

OK I guess we just try increasing the size of that column until MySQL doesn't moan anymore. Shall we say VARCHAR(500)?

You should just be able to update the table to VARCHAR(500) and rerun the migration - (v209 will then likely fail but meh we'll need yet another PR)

@ashimokawa
Copy link
Contributor

I can try after sleeping, I still find it weird that it fails.

@fnetX
Copy link
Contributor

fnetX commented Feb 14, 2022

2022/02/15 00:14:31 routers/common/db.go:34:InitDBEngine() [I] Backing off for 3 seconds
2022/02/15 00:14:34 routers/common/db.go:27:InitDBEngine() [I] ORM engine initialization attempt #3/10...
2022/02/15 00:14:34 cmd/web.go:153:runWeb() [I] PING DATABASE mysql
2022/02/15 00:14:34 ...ations/migrations.go:427:Migrate() [I] [SQL] SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=? [gitea version] - 1.543746ms
2022/02/15 00:14:34 models/db/engine.go:190:InitEngineWithMigration() [I] [SQL] SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ? [gitea version id] - 2.380338ms
2022/02/15 00:14:34 models/db/engine.go:190:InitEngineWithMigration() [I] [SQL] SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ? [gitea version version] - 1.877032ms
2022/02/15 00:14:34 [email protected]/engine.go:1139:Get() [I] [SQL] SELECT `id`, `version` FROM `version` WHERE `id`=? LIMIT 1 [1] - 1.567687ms
2022/02/15 00:14:34 ...ations/migrations.go:467:Migrate() [I] Migration[208]: Use base32.HexEncoding instead of base64 encoding for cred ID as it is case insensitive
2022/02/15 00:14:34 ...ations/migrations.go:50:Migrate() [I] [SQL] SELECT `TABLE_NAME`, `ENGINE`, `AUTO_INCREMENT`, `TABLE_COMMENT` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? AND (`ENGINE`='MyISAM' OR `ENGINE` = 'InnoDB' OR `ENGINE` = 'TokuDB') [gitea] - 3.828226ms
2022/02/15 00:14:34 ...s/migrations/v208.go:21:useBase32HexForCredIDInWebAuthnCredential() [I] [SQL] SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`, `COLUMN_KEY`, `EXTRA`, `COLUMN_COMMENT`, (INSTR(VERSION(), 'maria') > 0 && (SUBSTRING_INDEX(VERSION(), '.', 1) > 10 || (SUBSTRING_INDEX(VERSION(), '.', 1) = 10 && (SUBSTRING_INDEX(SUBSTRING(VERSION(), 4), '.', 1) > 2 || (SUBSTRING_INDEX(SUBSTRING(VERSION(), 4), '.', 1) = 2 && SUBSTRING_INDEX(SUBSTRING(VERSION(), 6), '-', 1) >= 7))))) AS NEEDS_QUOTE FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? ORDER BY `COLUMNS`.ORDINAL_POSITION [gitea webauthn_credential] - 2.895565ms
2022/02/15 00:14:34 ...s/migrations/v208.go:21:useBase32HexForCredIDInWebAuthnCredential() [I] [SQL] SELECT `INDEX_NAME`, `NON_UNIQUE`, `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? [gitea webauthn_credential] - 1.514677ms
2022/02/15 00:14:34 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column name but struct has not related field
2022/02/15 00:14:34 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column lower_name but struct has not related field
2022/02/15 00:14:34 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column user_id but struct has not related field
2022/02/15 00:14:34 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column public_key but struct has not related field
2022/02/15 00:14:34 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column attestation_type but struct has not related field
2022/02/15 00:14:34 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column aaguid but struct has not related field
2022/02/15 00:14:34 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column sign_count but struct has not related field
2022/02/15 00:14:34 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column clone_warning but struct has not related field
2022/02/15 00:14:34 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column created_unix but struct has not related field
2022/02/15 00:14:34 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column updated_unix but struct has not related field
2022/02/15 00:14:34 ...s/migrations/v208.go:28:useBase32HexForCredIDInWebAuthnCredential() [I] [SQL] SELECT `id`, `credential_id` FROM `webauthn_credential` ORDER BY id LIMIT 50 [] - 944.579µs
2022/02/15 00:14:34 ...ations/migrations.go:50:Migrate() [I] [SQL] UPDATE `webauthn_credential` SET `credential_id` = ?  [<long-string-here>] - 1.077227ms
2022/02/15 00:14:34 routers/common/db.go:33:InitDBEngine() [E] ORM engine initialization attempt #3/10 failed. Error: migrate: migration[208]: Use base32.HexEncoding instead of base64 encoding for cred ID as it is case insensitive failed: Error 1406: Data too long for column 'credential_id' at row 1
2022/02/15 00:14:34 routers/common/db.go:34:InitDBEngine() [I] Backing off for 3 seconds

I enabled the Gitea SQL log.

Look at the string I repalced with <long-string-here>. Piping it through wc -m (and -c for the record) returns 425. Is this helpful? I can try to gather more information, but I'm just getting into this topic, and need my portion of sleep sooner or later, too.

zeripath added a commit to zeripath/gitea that referenced this pull request Feb 14, 2022
MariaDB now asserts that 408 characters is too long for a VARCHAR(410)

I have no words.

See go-gitea#18756

Signed-off-by: Andrew Thornton <[email protected]>
zeripath added a commit to zeripath/gitea that referenced this pull request Feb 14, 2022
MariaDB now asserts that 408 characters is too long for a VARCHAR(410)

I have no words.

See go-gitea#18756

Signed-off-by: Andrew Thornton <[email protected]>
@zeripath
Copy link
Contributor Author

I've put up a PR to make it 500. Hopefully that will fix this.

#18771

Please try that and report if that works.

@fnetX
Copy link
Contributor

fnetX commented Feb 14, 2022

@zeripath I was trying to understand this migration, but my brain is failing at two points.

First of all, I notice that the SQL statement is just "UPDATE" without a where. This means, each record is always being updated, and all credential_id's are the very same in the end. Is this expected? I don't know much about webauthn, but it sounds off that the id is not unique?

Secondly, for some reason the size of the records grows with each batch of fifty. I kinda suppose it's re-encoding the data over and over again, that is also likely the reason for the failing size check. (Because of the former which updates all records?)

I'll check with varchar 500, but if the code behaves the way it appears to my tired eyes, this will just shift the failing to later.

@fnetX
Copy link
Contributor

fnetX commented Feb 15, 2022

... indeed: with varchar(500) it now tried inserting a string with 505 characters after a chunk of 50 credentials more.

@zeripath
Copy link
Contributor Author

Bloody hell you're right v208 is totally broken.

@fnetX
Copy link
Contributor

fnetX commented Feb 15, 2022

without knowing XORM, I suppose it should be

-                       _, err := x.Update(reg)
+                       _, err := x.ID(reg.ID).Update(reg)

or something like this?

@zeripath
Copy link
Contributor Author

I think the safest thing to do is to simply remigrate all of u2f keys

@lunny
Copy link
Member

lunny commented Feb 15, 2022

2022/02/15 00:14:34 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column name but struct has not related field
2022/02/15 00:14:34 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column lower_name but struct has not related field
2022/02/15 00:14:34 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column user_id but struct has not related field
2022/02/15 00:14:34 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column public_key but struct has not related field
2022/02/15 00:14:34 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column attestation_type but struct has not related field
2022/02/15 00:14:34 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column aaguid but struct has not related field
2022/02/15 00:14:34 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column sign_count but struct has not related field
2022/02/15 00:14:34 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column clone_warning but struct has not related field
2022/02/15 00:14:34 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column created_unix but struct has not related field
2022/02/15 00:14:34 routers/common/db.go:28:InitDBEngine() [W] Table webauthn_credential has column updated_unix but struct has not related field

The warning is also wired.

zeripath added a commit that referenced this pull request Feb 16, 2022
v208.go is seriously broken as it misses an ID() check. We need to no-op and remigrate all of the u2f keys.

See #18756

Signed-off-by: Andrew Thornton <[email protected]>
zeripath added a commit that referenced this pull request Feb 16, 2022
Backport #18770 

v208.go is seriously broken as it misses an ID() check. We need to no-op and remigrate all of the u2f keys.

See #18756

Signed-off-by: Andrew Thornton <[email protected]>
Chianina pushed a commit to Chianina/gitea that referenced this pull request Mar 28, 2022
v208.go is seriously broken as it misses an ID() check. We need to no-op and remigrate all of the u2f keys.

See go-gitea#18756

Signed-off-by: Andrew Thornton <[email protected]>
@go-gitea go-gitea locked and limited conversation to collaborators Apr 28, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. type/bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants