mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Add support for FIDO U2F (#3971)
* Add support for U2F Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add vendor library Add missing translations Signed-off-by: Jonas Franz <info@jonasfranz.software> * Minor improvements Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add U2F support for Firefox, Chrome (Android) by introducing a custom JS library Add U2F error handling Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add U2F login page to OAuth Signed-off-by: Jonas Franz <info@jonasfranz.software> * Move U2F user settings to a separate file Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add unit tests for u2f model Renamed u2f table name Signed-off-by: Jonas Franz <info@jonasfranz.software> * Fix problems caused by refactoring Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add U2F documentation Signed-off-by: Jonas Franz <info@jonasfranz.software> * Remove not needed console.log-s Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add default values to app.ini.sample Add FIDO U2F to comparison Signed-off-by: Jonas Franz <info@jonasfranz.software>
This commit is contained in:
		@@ -570,6 +570,14 @@ MAX_RESPONSE_ITEMS = 50
 | 
				
			|||||||
LANGS = en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,ja-JP,es-ES,pt-BR,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR
 | 
					LANGS = en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,ja-JP,es-ES,pt-BR,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR
 | 
				
			||||||
NAMES = English,简体中文,繁體中文(香港),繁體中文(台灣),Deutsch,français,Nederlands,latviešu,русский,日本語,español,português do Brasil,polski,български,italiano,suomi,Türkçe,čeština,српски,svenska,한국어
 | 
					NAMES = English,简体中文,繁體中文(香港),繁體中文(台灣),Deutsch,français,Nederlands,latviešu,русский,日本語,español,português do Brasil,polski,български,italiano,suomi,Türkçe,čeština,српски,svenska,한국어
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[U2F]
 | 
				
			||||||
 | 
					; Two Factor authentication with security keys
 | 
				
			||||||
 | 
					; https://developers.yubico.com/U2F/App_ID.html
 | 
				
			||||||
 | 
					APP_ID = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s
 | 
				
			||||||
 | 
					; Comma seperated list of truisted facets
 | 
				
			||||||
 | 
					TRUSTED_FACETS = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
; Used for datetimepicker
 | 
					; Used for datetimepicker
 | 
				
			||||||
[i18n.datelang]
 | 
					[i18n.datelang]
 | 
				
			||||||
en-US = en
 | 
					en-US = en
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -272,6 +272,10 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
 | 
				
			|||||||
- `MAX_GIT_DIFF_FILES`: **100**: Max number of files shown in diff view.
 | 
					- `MAX_GIT_DIFF_FILES`: **100**: Max number of files shown in diff view.
 | 
				
			||||||
- `GC_ARGS`: **\<empty\>**: Arguments for command `git gc`, e.g. `--aggressive --auto`.
 | 
					- `GC_ARGS`: **\<empty\>**: Arguments for command `git gc`, e.g. `--aggressive --auto`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## U2F (`U2F`)
 | 
				
			||||||
 | 
					- `APP_ID`: **`ROOT_URL`**: Declares the facet of the application. Requires HTTPS.
 | 
				
			||||||
 | 
					- `TRUSTED_FACETS`: List of additional facets which are trusted. This is not support by all browsers.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Markup (`markup`)
 | 
					## Markup (`markup`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Gitea can support Markup using external tools. The example below will add a markup named `asciidoc`.
 | 
					Gitea can support Markup using external tools. The example below will add a markup named `asciidoc`.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -535,6 +535,15 @@ _Symbols used in table:_
 | 
				
			|||||||
      <td>✓</td>
 | 
					      <td>✓</td>
 | 
				
			||||||
      <td>✓</td>
 | 
					      <td>✓</td>
 | 
				
			||||||
    </tr>
 | 
					    </tr>
 | 
				
			||||||
 | 
					    <tr>
 | 
				
			||||||
 | 
					      <td>FIDO U2F (2FA)</td>
 | 
				
			||||||
 | 
					      <td>✓</td>
 | 
				
			||||||
 | 
					      <td>✘</td>
 | 
				
			||||||
 | 
					      <td>✓</td>
 | 
				
			||||||
 | 
					      <td>✓</td>
 | 
				
			||||||
 | 
					      <td>✓</td>
 | 
				
			||||||
 | 
					      <td>✓</td>
 | 
				
			||||||
 | 
					    </tr>
 | 
				
			||||||
    <tr>
 | 
					    <tr>
 | 
				
			||||||
      <td>Webhook support</td>
 | 
					      <td>Webhook support</td>
 | 
				
			||||||
      <td>✓</td>
 | 
					      <td>✓</td>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1237,3 +1237,25 @@ func IsErrExternalLoginUserNotExist(err error) bool {
 | 
				
			|||||||
func (err ErrExternalLoginUserNotExist) Error() string {
 | 
					func (err ErrExternalLoginUserNotExist) Error() string {
 | 
				
			||||||
	return fmt.Sprintf("external login user link does not exists [userID: %d, loginSourceID: %d]", err.UserID, err.LoginSourceID)
 | 
						return fmt.Sprintf("external login user link does not exists [userID: %d, loginSourceID: %d]", err.UserID, err.LoginSourceID)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ____ ________________________________              .__          __                 __  .__
 | 
				
			||||||
 | 
					// |    |   \_____  \_   _____/\______   \ ____   ____ |__| _______/  |_____________ _/  |_|__| ____   ____
 | 
				
			||||||
 | 
					// |    |   //  ____/|    __)   |       _// __ \ / ___\|  |/  ___/\   __\_  __ \__  \\   __\  |/  _ \ /    \
 | 
				
			||||||
 | 
					// |    |  //       \|     \    |    |   \  ___// /_/  >  |\___ \  |  |  |  | \// __ \|  | |  (  <_> )   |  \
 | 
				
			||||||
 | 
					// |______/ \_______ \___  /    |____|_  /\___  >___  /|__/____  > |__|  |__|  (____  /__| |__|\____/|___|  /
 | 
				
			||||||
 | 
					// \/   \/            \/     \/_____/         \/                   \/                    \/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ErrU2FRegistrationNotExist represents a "ErrU2FRegistrationNotExist" kind of error.
 | 
				
			||||||
 | 
					type ErrU2FRegistrationNotExist struct {
 | 
				
			||||||
 | 
						ID int64
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (err ErrU2FRegistrationNotExist) Error() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("U2F registration does not exist [id: %d]", err.ID)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsErrU2FRegistrationNotExist checks if an error is a ErrU2FRegistrationNotExist.
 | 
				
			||||||
 | 
					func IsErrU2FRegistrationNotExist(err error) bool {
 | 
				
			||||||
 | 
						_, ok := err.(ErrU2FRegistrationNotExist)
 | 
				
			||||||
 | 
						return ok
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										7
									
								
								models/fixtures/u2f_registration.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								models/fixtures/u2f_registration.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					-
 | 
				
			||||||
 | 
					  id: 1
 | 
				
			||||||
 | 
					  name: "U2F Key"
 | 
				
			||||||
 | 
					  user_id: 1
 | 
				
			||||||
 | 
					  counter: 0
 | 
				
			||||||
 | 
					  created_unix: 946684800
 | 
				
			||||||
 | 
					  updated_unix: 946684800
 | 
				
			||||||
@@ -182,6 +182,8 @@ var migrations = []Migration{
 | 
				
			|||||||
	NewMigration("add language column for user setting", addLanguageSetting),
 | 
						NewMigration("add language column for user setting", addLanguageSetting),
 | 
				
			||||||
	// v64 -> v65
 | 
						// v64 -> v65
 | 
				
			||||||
	NewMigration("add multiple assignees", addMultipleAssignees),
 | 
						NewMigration("add multiple assignees", addMultipleAssignees),
 | 
				
			||||||
 | 
						// v65 -> v66
 | 
				
			||||||
 | 
						NewMigration("add u2f", addU2FReg),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Migrate database to current version
 | 
					// Migrate database to current version
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										19
									
								
								models/migrations/v65.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								models/migrations/v65.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					package migrations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
 | 
						"github.com/go-xorm/xorm"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func addU2FReg(x *xorm.Engine) error {
 | 
				
			||||||
 | 
						type U2FRegistration struct {
 | 
				
			||||||
 | 
							ID          int64 `xorm:"pk autoincr"`
 | 
				
			||||||
 | 
							Name        string
 | 
				
			||||||
 | 
							UserID      int64 `xorm:"INDEX"`
 | 
				
			||||||
 | 
							Raw         []byte
 | 
				
			||||||
 | 
							Counter     uint32
 | 
				
			||||||
 | 
							CreatedUnix util.TimeStamp `xorm:"INDEX created"`
 | 
				
			||||||
 | 
							UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return x.Sync2(&U2FRegistration{})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -120,6 +120,7 @@ func init() {
 | 
				
			|||||||
		new(LFSLock),
 | 
							new(LFSLock),
 | 
				
			||||||
		new(Reaction),
 | 
							new(Reaction),
 | 
				
			||||||
		new(IssueAssignees),
 | 
							new(IssueAssignees),
 | 
				
			||||||
 | 
							new(U2FRegistration),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	gonicNames := []string{"SSL", "UID"}
 | 
						gonicNames := []string{"SSL", "UID"}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										120
									
								
								models/u2f.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								models/u2f.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,120 @@
 | 
				
			|||||||
 | 
					// Copyright 2018 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 models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/tstranex/u2f"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// U2FRegistration represents the registration data and counter of a security key
 | 
				
			||||||
 | 
					type U2FRegistration struct {
 | 
				
			||||||
 | 
						ID          int64 `xorm:"pk autoincr"`
 | 
				
			||||||
 | 
						Name        string
 | 
				
			||||||
 | 
						UserID      int64 `xorm:"INDEX"`
 | 
				
			||||||
 | 
						Raw         []byte
 | 
				
			||||||
 | 
						Counter     uint32
 | 
				
			||||||
 | 
						CreatedUnix util.TimeStamp `xorm:"INDEX created"`
 | 
				
			||||||
 | 
						UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TableName returns a better table name for U2FRegistration
 | 
				
			||||||
 | 
					func (reg U2FRegistration) TableName() string {
 | 
				
			||||||
 | 
						return "u2f_registration"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Parse will convert the db entry U2FRegistration to an u2f.Registration struct
 | 
				
			||||||
 | 
					func (reg *U2FRegistration) Parse() (*u2f.Registration, error) {
 | 
				
			||||||
 | 
						r := new(u2f.Registration)
 | 
				
			||||||
 | 
						return r, r.UnmarshalBinary(reg.Raw)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (reg *U2FRegistration) updateCounter(e Engine) error {
 | 
				
			||||||
 | 
						_, err := e.ID(reg.ID).Cols("counter").Update(reg)
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdateCounter will update the database value of counter
 | 
				
			||||||
 | 
					func (reg *U2FRegistration) UpdateCounter() error {
 | 
				
			||||||
 | 
						return reg.updateCounter(x)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// U2FRegistrationList is a list of *U2FRegistration
 | 
				
			||||||
 | 
					type U2FRegistrationList []*U2FRegistration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ToRegistrations will convert all U2FRegistrations to u2f.Registrations
 | 
				
			||||||
 | 
					func (list U2FRegistrationList) ToRegistrations() []u2f.Registration {
 | 
				
			||||||
 | 
						regs := make([]u2f.Registration, len(list))
 | 
				
			||||||
 | 
						for _, reg := range list {
 | 
				
			||||||
 | 
							r, err := reg.Parse()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Fatal(4, "parsing u2f registration: %v", err)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							regs = append(regs, *r)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return regs
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getU2FRegistrationsByUID(e Engine, uid int64) (U2FRegistrationList, error) {
 | 
				
			||||||
 | 
						regs := make(U2FRegistrationList, 0)
 | 
				
			||||||
 | 
						return regs, e.Where("user_id = ?", uid).Find(®s)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetU2FRegistrationByID returns U2F registration by id
 | 
				
			||||||
 | 
					func GetU2FRegistrationByID(id int64) (*U2FRegistration, error) {
 | 
				
			||||||
 | 
						return getU2FRegistrationByID(x, id)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getU2FRegistrationByID(e Engine, id int64) (*U2FRegistration, error) {
 | 
				
			||||||
 | 
						reg := new(U2FRegistration)
 | 
				
			||||||
 | 
						if found, err := e.ID(id).Get(reg); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						} else if !found {
 | 
				
			||||||
 | 
							return nil, ErrU2FRegistrationNotExist{ID: id}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return reg, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetU2FRegistrationsByUID returns all U2F registrations of the given user
 | 
				
			||||||
 | 
					func GetU2FRegistrationsByUID(uid int64) (U2FRegistrationList, error) {
 | 
				
			||||||
 | 
						return getU2FRegistrationsByUID(x, uid)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func createRegistration(e Engine, user *User, name string, reg *u2f.Registration) (*U2FRegistration, error) {
 | 
				
			||||||
 | 
						raw, err := reg.MarshalBinary()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						r := &U2FRegistration{
 | 
				
			||||||
 | 
							UserID:  user.ID,
 | 
				
			||||||
 | 
							Name:    name,
 | 
				
			||||||
 | 
							Counter: 0,
 | 
				
			||||||
 | 
							Raw:     raw,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, err = e.InsertOne(r)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return r, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CreateRegistration will create a new U2FRegistration from the given Registration
 | 
				
			||||||
 | 
					func CreateRegistration(user *User, name string, reg *u2f.Registration) (*U2FRegistration, error) {
 | 
				
			||||||
 | 
						return createRegistration(x, user, name, reg)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeleteRegistration will delete U2FRegistration
 | 
				
			||||||
 | 
					func DeleteRegistration(reg *U2FRegistration) error {
 | 
				
			||||||
 | 
						return deleteRegistration(x, reg)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func deleteRegistration(e Engine, reg *U2FRegistration) error {
 | 
				
			||||||
 | 
						_, err := e.Delete(reg)
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										61
									
								
								models/u2f_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								models/u2f_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
				
			|||||||
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
						"github.com/tstranex/u2f"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestGetU2FRegistrationByID(t *testing.T) {
 | 
				
			||||||
 | 
						assert.NoError(t, PrepareTestDatabase())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res, err := GetU2FRegistrationByID(1)
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
						assert.Equal(t, "U2F Key", res.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = GetU2FRegistrationByID(342432)
 | 
				
			||||||
 | 
						assert.Error(t, err)
 | 
				
			||||||
 | 
						assert.True(t, IsErrU2FRegistrationNotExist(err))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestGetU2FRegistrationsByUID(t *testing.T) {
 | 
				
			||||||
 | 
						assert.NoError(t, PrepareTestDatabase())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res, err := GetU2FRegistrationsByUID(1)
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
						assert.Len(t, res, 1)
 | 
				
			||||||
 | 
						assert.Equal(t, "U2F Key", res[0].Name)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestU2FRegistration_TableName(t *testing.T) {
 | 
				
			||||||
 | 
						assert.Equal(t, "u2f_registration", U2FRegistration{}.TableName())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestU2FRegistration_UpdateCounter(t *testing.T) {
 | 
				
			||||||
 | 
						assert.NoError(t, PrepareTestDatabase())
 | 
				
			||||||
 | 
						reg := AssertExistsAndLoadBean(t, &U2FRegistration{ID: 1}).(*U2FRegistration)
 | 
				
			||||||
 | 
						reg.Counter = 1
 | 
				
			||||||
 | 
						assert.NoError(t, reg.UpdateCounter())
 | 
				
			||||||
 | 
						AssertExistsIf(t, true, &U2FRegistration{ID: 1, Counter: 1})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCreateRegistration(t *testing.T) {
 | 
				
			||||||
 | 
						assert.NoError(t, PrepareTestDatabase())
 | 
				
			||||||
 | 
						user := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res, err := CreateRegistration(user, "U2F Created Key", &u2f.Registration{Raw: []byte("Test")})
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
						assert.Equal(t, "U2F Created Key", res.Name)
 | 
				
			||||||
 | 
						assert.Equal(t, []byte("Test"), res.Raw)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						AssertExistsIf(t, true, &U2FRegistration{Name: "U2F Created Key", UserID: user.ID})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestDeleteRegistration(t *testing.T) {
 | 
				
			||||||
 | 
						assert.NoError(t, PrepareTestDatabase())
 | 
				
			||||||
 | 
						reg := AssertExistsAndLoadBean(t, &U2FRegistration{ID: 1}).(*U2FRegistration)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.NoError(t, DeleteRegistration(reg))
 | 
				
			||||||
 | 
						AssertNotExistsBean(t, &U2FRegistration{ID: 1})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -211,3 +211,23 @@ type TwoFactorScratchAuthForm struct {
 | 
				
			|||||||
func (f *TwoFactorScratchAuthForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
 | 
					func (f *TwoFactorScratchAuthForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
 | 
				
			||||||
	return validate(errs, ctx.Data, f, ctx.Locale)
 | 
						return validate(errs, ctx.Data, f, ctx.Locale)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// U2FRegistrationForm for reserving an U2F name
 | 
				
			||||||
 | 
					type U2FRegistrationForm struct {
 | 
				
			||||||
 | 
						Name string `binding:"Required"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Validate valideates the fields
 | 
				
			||||||
 | 
					func (f *U2FRegistrationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
 | 
				
			||||||
 | 
						return validate(errs, ctx.Data, f, ctx.Locale)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// U2FDeleteForm for deleting U2F keys
 | 
				
			||||||
 | 
					type U2FDeleteForm struct {
 | 
				
			||||||
 | 
						ID int64 `binding:"Required"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Validate valideates the fields
 | 
				
			||||||
 | 
					func (f *U2FDeleteForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
 | 
				
			||||||
 | 
						return validate(errs, ctx.Data, f, ctx.Locale)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -521,6 +521,11 @@ var (
 | 
				
			|||||||
		MaxResponseItems:      50,
 | 
							MaxResponseItems:      50,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						U2F = struct {
 | 
				
			||||||
 | 
							AppID         string
 | 
				
			||||||
 | 
							TrustedFacets []string
 | 
				
			||||||
 | 
						}{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// I18n settings
 | 
						// I18n settings
 | 
				
			||||||
	Langs     []string
 | 
						Langs     []string
 | 
				
			||||||
	Names     []string
 | 
						Names     []string
 | 
				
			||||||
@@ -1135,6 +1140,9 @@ func NewContext() {
 | 
				
			|||||||
			IsInputFile:    sec.Key("IS_INPUT_FILE").MustBool(false),
 | 
								IsInputFile:    sec.Key("IS_INPUT_FILE").MustBool(false),
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						sec = Cfg.Section("U2F")
 | 
				
			||||||
 | 
						U2F.TrustedFacets, _ = shellquote.Split(sec.Key("TRUSTED_FACETS").MustString(strings.TrimRight(AppURL, "/")))
 | 
				
			||||||
 | 
						U2F.AppID = sec.Key("APP_ID").MustString(strings.TrimRight(AppURL, "/"))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Service settings
 | 
					// Service settings
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,6 +31,19 @@ twofa = Two-Factor Authentication
 | 
				
			|||||||
twofa_scratch = Two-Factor Scratch Code
 | 
					twofa_scratch = Two-Factor Scratch Code
 | 
				
			||||||
passcode = Passcode
 | 
					passcode = Passcode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					u2f_insert_key = Insert your security key
 | 
				
			||||||
 | 
					u2f_sign_in = Press the button on your security key. If you can't find a button, re-insert it.
 | 
				
			||||||
 | 
					u2f_press_button = Please press the button on your security key…
 | 
				
			||||||
 | 
					u2f_use_twofa = Use a two-factor code from your phone
 | 
				
			||||||
 | 
					u2f_error = We can't read your security key!
 | 
				
			||||||
 | 
					u2f_unsupported_browser = Your browser don't support U2F keys. Please try another browser.
 | 
				
			||||||
 | 
					u2f_error_1 = An unknown error occured. Please retry.
 | 
				
			||||||
 | 
					u2f_error_2 = Please make sure that you're using an encrypted connection (https://) and visiting the correct URL.
 | 
				
			||||||
 | 
					u2f_error_3 = The server could not proceed your request.
 | 
				
			||||||
 | 
					u2f_error_4 = The presented key is not eligible for this request. If you try to register it, make sure that the key isn't already registered.
 | 
				
			||||||
 | 
					u2f_error_5 = Timeout reached before your key could be read. Please reload to retry.
 | 
				
			||||||
 | 
					u2f_reload = Reload
 | 
				
			||||||
 | 
					
 | 
				
			||||||
repository = Repository
 | 
					repository = Repository
 | 
				
			||||||
organization = Organization
 | 
					organization = Organization
 | 
				
			||||||
mirror = Mirror
 | 
					mirror = Mirror
 | 
				
			||||||
@@ -320,6 +333,7 @@ twofa = Two-Factor Authentication
 | 
				
			|||||||
account_link = Linked Accounts
 | 
					account_link = Linked Accounts
 | 
				
			||||||
organization = Organizations
 | 
					organization = Organizations
 | 
				
			||||||
uid = Uid
 | 
					uid = Uid
 | 
				
			||||||
 | 
					u2f = Security Keys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public_profile = Public Profile
 | 
					public_profile = Public Profile
 | 
				
			||||||
profile_desc = Your email address will be used for notifications and other operations.
 | 
					profile_desc = Your email address will be used for notifications and other operations.
 | 
				
			||||||
@@ -449,6 +463,14 @@ then_enter_passcode = And enter the passcode shown in the application:
 | 
				
			|||||||
passcode_invalid = The passcode is incorrect. Try again.
 | 
					passcode_invalid = The passcode is incorrect. Try again.
 | 
				
			||||||
twofa_enrolled = Your account has been enrolled into two-factor authentication. Store your scratch token (%s) in a safe place as it is only shown once!
 | 
					twofa_enrolled = Your account has been enrolled into two-factor authentication. Store your scratch token (%s) in a safe place as it is only shown once!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					u2f_desc = Security keys are hardware devices containing cryptograhic keys. They could be used for two factor authentication. The security key must support the <a href="https://fidoalliance.org/">FIDO U2F</a> standard.
 | 
				
			||||||
 | 
					u2f_require_twofa = Two-Factor-Authentication must be enrolled in order to use security keys.
 | 
				
			||||||
 | 
					u2f_register_key = Add Security Key
 | 
				
			||||||
 | 
					u2f_nickname = Nickname
 | 
				
			||||||
 | 
					u2f_press_button = Press the button on your security key to register it.
 | 
				
			||||||
 | 
					u2f_delete_key = Remove Security Key
 | 
				
			||||||
 | 
					u2f_delete_key_desc= If you remove a security key you cannot login with it anymore. Are you sure?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
manage_account_links = Manage Linked Accounts
 | 
					manage_account_links = Manage Linked Accounts
 | 
				
			||||||
manage_account_links_desc = These external accounts are linked to your Gitea account.
 | 
					manage_account_links_desc = These external accounts are linked to your Gitea account.
 | 
				
			||||||
account_links_not_available = There are currently no external accounts linked to your Gitea account.
 | 
					account_links_not_available = There are currently no external accounts linked to your Gitea account.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1432,6 +1432,130 @@ function initCodeView() {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function initU2FAuth() {
 | 
				
			||||||
 | 
					    if($('#wait-for-key').length === 0) {
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    u2fApi.ensureSupport()
 | 
				
			||||||
 | 
					        .then(function () {
 | 
				
			||||||
 | 
					            $.getJSON('/user/u2f/challenge').success(function(req) {
 | 
				
			||||||
 | 
					                u2fApi.sign(req.appId, req.challenge, req.registeredKeys, 30)
 | 
				
			||||||
 | 
					                    .then(u2fSigned)
 | 
				
			||||||
 | 
					                    .catch(function (err) {
 | 
				
			||||||
 | 
					                        if(err === undefined) {
 | 
				
			||||||
 | 
					                            u2fError(1);
 | 
				
			||||||
 | 
					                            return
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        u2fError(err.metaData.code);
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }).catch(function () {
 | 
				
			||||||
 | 
					            // Fallback in case browser do not support U2F
 | 
				
			||||||
 | 
					            window.location.href = "/user/two_factor"
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					function u2fSigned(resp) {
 | 
				
			||||||
 | 
					    $.ajax({
 | 
				
			||||||
 | 
					        url:'/user/u2f/sign',
 | 
				
			||||||
 | 
					        type:"POST",
 | 
				
			||||||
 | 
					        headers: {"X-Csrf-Token": csrf},
 | 
				
			||||||
 | 
					        data: JSON.stringify(resp),
 | 
				
			||||||
 | 
					        contentType:"application/json; charset=utf-8",
 | 
				
			||||||
 | 
					    }).done(function(res){
 | 
				
			||||||
 | 
					        window.location.replace(res);
 | 
				
			||||||
 | 
					    }).fail(function (xhr, textStatus) {
 | 
				
			||||||
 | 
					        u2fError(1);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function u2fRegistered(resp) {
 | 
				
			||||||
 | 
					    if (checkError(resp)) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    $.ajax({
 | 
				
			||||||
 | 
					        url:'/user/settings/security/u2f/register',
 | 
				
			||||||
 | 
					        type:"POST",
 | 
				
			||||||
 | 
					        headers: {"X-Csrf-Token": csrf},
 | 
				
			||||||
 | 
					        data: JSON.stringify(resp),
 | 
				
			||||||
 | 
					        contentType:"application/json; charset=utf-8",
 | 
				
			||||||
 | 
					        success: function(){
 | 
				
			||||||
 | 
					            window.location.reload();
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        fail: function (xhr, textStatus) {
 | 
				
			||||||
 | 
					            u2fError(1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function checkError(resp) {
 | 
				
			||||||
 | 
					    if (!('errorCode' in resp)) {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (resp.errorCode === 0) {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    u2fError(resp.errorCode);
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function u2fError(errorType) {
 | 
				
			||||||
 | 
					    var u2fErrors = {
 | 
				
			||||||
 | 
					        'browser': $('#unsupported-browser'),
 | 
				
			||||||
 | 
					        1: $('#u2f-error-1'),
 | 
				
			||||||
 | 
					        2: $('#u2f-error-2'),
 | 
				
			||||||
 | 
					        3: $('#u2f-error-3'),
 | 
				
			||||||
 | 
					        4: $('#u2f-error-4'),
 | 
				
			||||||
 | 
					        5: $('.u2f-error-5')
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    u2fErrors[errorType].removeClass('hide');
 | 
				
			||||||
 | 
					    for(var type in u2fErrors){
 | 
				
			||||||
 | 
					        if(type != errorType){
 | 
				
			||||||
 | 
					            u2fErrors[type].addClass('hide');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    $('#u2f-error').modal('show');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function initU2FRegister() {
 | 
				
			||||||
 | 
					    $('#register-device').modal({allowMultiple: false});
 | 
				
			||||||
 | 
					    $('#u2f-error').modal({allowMultiple: false});
 | 
				
			||||||
 | 
					    $('#register-security-key').on('click', function(e) {
 | 
				
			||||||
 | 
					        e.preventDefault();
 | 
				
			||||||
 | 
					        u2fApi.ensureSupport()
 | 
				
			||||||
 | 
					            .then(u2fRegisterRequest)
 | 
				
			||||||
 | 
					            .catch(function() {
 | 
				
			||||||
 | 
					                u2fError('browser');
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function u2fRegisterRequest() {
 | 
				
			||||||
 | 
					    $.post("/user/settings/security/u2f/request_register", {
 | 
				
			||||||
 | 
					        "_csrf": csrf,
 | 
				
			||||||
 | 
					        "name": $('#nickname').val()
 | 
				
			||||||
 | 
					    }).success(function(req) {
 | 
				
			||||||
 | 
					        $("#nickname").closest("div.field").removeClass("error");
 | 
				
			||||||
 | 
					        $('#register-device').modal('show');
 | 
				
			||||||
 | 
					        if(req.registeredKeys === null) {
 | 
				
			||||||
 | 
					            req.registeredKeys = []
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        u2fApi.register(req.appId, req.registerRequests, req.registeredKeys, 30)
 | 
				
			||||||
 | 
					            .then(u2fRegistered)
 | 
				
			||||||
 | 
					            .catch(function (reason) {
 | 
				
			||||||
 | 
					                if(reason === undefined) {
 | 
				
			||||||
 | 
					                    u2fError(1);
 | 
				
			||||||
 | 
					                    return
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                u2fError(reason.metaData.code);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					    }).fail(function(xhr, status, error) {
 | 
				
			||||||
 | 
					        if(xhr.status === 409) {
 | 
				
			||||||
 | 
					            $("#nickname").closest("div.field").addClass("error");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$(document).ready(function () {
 | 
					$(document).ready(function () {
 | 
				
			||||||
    csrf = $('meta[name=_csrf]').attr("content");
 | 
					    csrf = $('meta[name=_csrf]').attr("content");
 | 
				
			||||||
    suburl = $('meta[name=_suburl]').attr("content");
 | 
					    suburl = $('meta[name=_suburl]').attr("content");
 | 
				
			||||||
@@ -1643,6 +1767,8 @@ $(document).ready(function () {
 | 
				
			|||||||
    initCtrlEnterSubmit();
 | 
					    initCtrlEnterSubmit();
 | 
				
			||||||
    initNavbarContentToggle();
 | 
					    initNavbarContentToggle();
 | 
				
			||||||
    initTopicbar();
 | 
					    initTopicbar();
 | 
				
			||||||
 | 
					    initU2FAuth();
 | 
				
			||||||
 | 
					    initU2FRegister();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Repo clone url.
 | 
					    // Repo clone url.
 | 
				
			||||||
    if ($('#repo-clone-url').length > 0) {
 | 
					    if ($('#repo-clone-url').length > 0) {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										5
									
								
								public/vendor/librejs.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								public/vendor/librejs.html
									
									
									
									
										vendored
									
									
								
							@@ -110,6 +110,11 @@
 | 
				
			|||||||
          <td><a href="https://github.com/mozilla/pdf.js/blob/master/LICENSE">Apache-2.0-only</a></td>
 | 
					          <td><a href="https://github.com/mozilla/pdf.js/blob/master/LICENSE">Apache-2.0-only</a></td>
 | 
				
			||||||
          <td><a href="https://github.com/mozilla/pdf.js/archive/v1.4.20.tar.gz">pdf.js-v1.4.20.tar.gz</a></td>
 | 
					          <td><a href="https://github.com/mozilla/pdf.js/archive/v1.4.20.tar.gz">pdf.js-v1.4.20.tar.gz</a></td>
 | 
				
			||||||
        </tr>
 | 
					        </tr>
 | 
				
			||||||
 | 
							<tr>
 | 
				
			||||||
 | 
							  <td><a href="/vendor/plugins/u2f/">u2f-api</a></td>
 | 
				
			||||||
 | 
							  <td><a href="https://github.com/go-gitea/u2f-api/blob/master/LICENSE">Expat</a></td>
 | 
				
			||||||
 | 
							  <td><a href="https://github.com/go-gitea/u2f-api/archive/v1.0.8.zip">u2f-api-1.0.8.zip</a></td>
 | 
				
			||||||
 | 
							</tr>
 | 
				
			||||||
        <tr>
 | 
					        <tr>
 | 
				
			||||||
          <td><a href="/vendor/assets/font-awesome/fonts/">font-awesome - fonts</a></td>
 | 
					          <td><a href="/vendor/assets/font-awesome/fonts/">font-awesome - fonts</a></td>
 | 
				
			||||||
          <td><a href="http://fontawesome.io/license/">OFL</a></td>
 | 
					          <td><a href="http://fontawesome.io/license/">OFL</a></td>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								public/vendor/plugins/u2f/index.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								public/vendor/plugins/u2f/index.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -5,6 +5,8 @@
 | 
				
			|||||||
package routes
 | 
					package routes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/gob"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
@@ -37,12 +39,13 @@ import (
 | 
				
			|||||||
	"github.com/go-macaron/i18n"
 | 
						"github.com/go-macaron/i18n"
 | 
				
			||||||
	"github.com/go-macaron/session"
 | 
						"github.com/go-macaron/session"
 | 
				
			||||||
	"github.com/go-macaron/toolbox"
 | 
						"github.com/go-macaron/toolbox"
 | 
				
			||||||
 | 
						"github.com/tstranex/u2f"
 | 
				
			||||||
	"gopkg.in/macaron.v1"
 | 
						"gopkg.in/macaron.v1"
 | 
				
			||||||
	"net/http"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewMacaron initializes Macaron instance.
 | 
					// NewMacaron initializes Macaron instance.
 | 
				
			||||||
func NewMacaron() *macaron.Macaron {
 | 
					func NewMacaron() *macaron.Macaron {
 | 
				
			||||||
 | 
						gob.Register(&u2f.Challenge{})
 | 
				
			||||||
	m := macaron.New()
 | 
						m := macaron.New()
 | 
				
			||||||
	if !setting.DisableRouterLog {
 | 
						if !setting.DisableRouterLog {
 | 
				
			||||||
		m.Use(macaron.Logger())
 | 
							m.Use(macaron.Logger())
 | 
				
			||||||
@@ -214,6 +217,12 @@ func RegisterRoutes(m *macaron.Macaron) {
 | 
				
			|||||||
			m.Get("/scratch", user.TwoFactorScratch)
 | 
								m.Get("/scratch", user.TwoFactorScratch)
 | 
				
			||||||
			m.Post("/scratch", bindIgnErr(auth.TwoFactorScratchAuthForm{}), user.TwoFactorScratchPost)
 | 
								m.Post("/scratch", bindIgnErr(auth.TwoFactorScratchAuthForm{}), user.TwoFactorScratchPost)
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
 | 
							m.Group("/u2f", func() {
 | 
				
			||||||
 | 
								m.Get("", user.U2F)
 | 
				
			||||||
 | 
								m.Get("/challenge", user.U2FChallenge)
 | 
				
			||||||
 | 
								m.Post("/sign", bindIgnErr(u2f.SignResponse{}), user.U2FSign)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
	}, reqSignOut)
 | 
						}, reqSignOut)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	m.Group("/user/settings", func() {
 | 
						m.Group("/user/settings", func() {
 | 
				
			||||||
@@ -235,6 +244,11 @@ func RegisterRoutes(m *macaron.Macaron) {
 | 
				
			|||||||
				m.Get("/enroll", userSetting.EnrollTwoFactor)
 | 
									m.Get("/enroll", userSetting.EnrollTwoFactor)
 | 
				
			||||||
				m.Post("/enroll", bindIgnErr(auth.TwoFactorAuthForm{}), userSetting.EnrollTwoFactorPost)
 | 
									m.Post("/enroll", bindIgnErr(auth.TwoFactorAuthForm{}), userSetting.EnrollTwoFactorPost)
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
 | 
								m.Group("/u2f", func() {
 | 
				
			||||||
 | 
									m.Post("/request_register", bindIgnErr(auth.U2FRegistrationForm{}), userSetting.U2FRegister)
 | 
				
			||||||
 | 
									m.Post("/register", bindIgnErr(u2f.RegisterResponse{}), userSetting.U2FRegisterPost)
 | 
				
			||||||
 | 
									m.Post("/delete", bindIgnErr(auth.U2FDeleteForm{}), userSetting.U2FDelete)
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
			m.Group("/openid", func() {
 | 
								m.Group("/openid", func() {
 | 
				
			||||||
				m.Post("", bindIgnErr(auth.AddOpenIDForm{}), userSetting.OpenIDPost)
 | 
									m.Post("", bindIgnErr(auth.AddOpenIDForm{}), userSetting.OpenIDPost)
 | 
				
			||||||
				m.Post("/delete", userSetting.DeleteOpenID)
 | 
									m.Post("/delete", userSetting.DeleteOpenID)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,6 +21,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"github.com/go-macaron/captcha"
 | 
						"github.com/go-macaron/captcha"
 | 
				
			||||||
	"github.com/markbates/goth"
 | 
						"github.com/markbates/goth"
 | 
				
			||||||
 | 
						"github.com/tstranex/u2f"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
@@ -35,6 +36,7 @@ const (
 | 
				
			|||||||
	tplTwofa          base.TplName = "user/auth/twofa"
 | 
						tplTwofa          base.TplName = "user/auth/twofa"
 | 
				
			||||||
	tplTwofaScratch   base.TplName = "user/auth/twofa_scratch"
 | 
						tplTwofaScratch   base.TplName = "user/auth/twofa_scratch"
 | 
				
			||||||
	tplLinkAccount    base.TplName = "user/auth/link_account"
 | 
						tplLinkAccount    base.TplName = "user/auth/link_account"
 | 
				
			||||||
 | 
						tplU2F            base.TplName = "user/auth/u2f"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// AutoSignIn reads cookie and try to auto-login.
 | 
					// AutoSignIn reads cookie and try to auto-login.
 | 
				
			||||||
@@ -159,7 +161,6 @@ func SignInPost(ctx *context.Context, form auth.SignInForm) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// If this user is enrolled in 2FA, we can't sign the user in just yet.
 | 
						// If this user is enrolled in 2FA, we can't sign the user in just yet.
 | 
				
			||||||
	// Instead, redirect them to the 2FA authentication page.
 | 
						// Instead, redirect them to the 2FA authentication page.
 | 
				
			||||||
	_, err = models.GetTwoFactorByUID(u.ID)
 | 
						_, err = models.GetTwoFactorByUID(u.ID)
 | 
				
			||||||
@@ -175,6 +176,13 @@ func SignInPost(ctx *context.Context, form auth.SignInForm) {
 | 
				
			|||||||
	// User needs to use 2FA, save data and redirect to 2FA page.
 | 
						// User needs to use 2FA, save data and redirect to 2FA page.
 | 
				
			||||||
	ctx.Session.Set("twofaUid", u.ID)
 | 
						ctx.Session.Set("twofaUid", u.ID)
 | 
				
			||||||
	ctx.Session.Set("twofaRemember", form.Remember)
 | 
						ctx.Session.Set("twofaRemember", form.Remember)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						regs, err := models.GetU2FRegistrationsByUID(u.ID)
 | 
				
			||||||
 | 
						if err == nil && len(regs) > 0 {
 | 
				
			||||||
 | 
							ctx.Redirect(setting.AppSubURL + "/user/u2f")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.Redirect(setting.AppSubURL + "/user/two_factor")
 | 
						ctx.Redirect(setting.AppSubURL + "/user/two_factor")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -317,12 +325,115 @@ func TwoFactorScratchPost(ctx *context.Context, form auth.TwoFactorScratchAuthFo
 | 
				
			|||||||
	ctx.RenderWithErr(ctx.Tr("auth.twofa_scratch_token_incorrect"), tplTwofaScratch, auth.TwoFactorScratchAuthForm{})
 | 
						ctx.RenderWithErr(ctx.Tr("auth.twofa_scratch_token_incorrect"), tplTwofaScratch, auth.TwoFactorScratchAuthForm{})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// U2F shows the U2F login page
 | 
				
			||||||
 | 
					func U2F(ctx *context.Context) {
 | 
				
			||||||
 | 
						ctx.Data["Title"] = ctx.Tr("twofa")
 | 
				
			||||||
 | 
						ctx.Data["RequireU2F"] = true
 | 
				
			||||||
 | 
						// Check auto-login.
 | 
				
			||||||
 | 
						if checkAutoLogin(ctx) {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Ensure user is in a 2FA session.
 | 
				
			||||||
 | 
						if ctx.Session.Get("twofaUid") == nil {
 | 
				
			||||||
 | 
							ctx.ServerError("UserSignIn", errors.New("not in U2F session"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.HTML(200, tplU2F)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// U2FChallenge submits a sign challenge to the browser
 | 
				
			||||||
 | 
					func U2FChallenge(ctx *context.Context) {
 | 
				
			||||||
 | 
						// Ensure user is in a U2F session.
 | 
				
			||||||
 | 
						idSess := ctx.Session.Get("twofaUid")
 | 
				
			||||||
 | 
						if idSess == nil {
 | 
				
			||||||
 | 
							ctx.ServerError("UserSignIn", errors.New("not in U2F session"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						id := idSess.(int64)
 | 
				
			||||||
 | 
						regs, err := models.GetU2FRegistrationsByUID(id)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.ServerError("UserSignIn", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(regs) == 0 {
 | 
				
			||||||
 | 
							ctx.ServerError("UserSignIn", errors.New("no device registered"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						challenge, err := u2f.NewChallenge(setting.U2F.AppID, setting.U2F.TrustedFacets)
 | 
				
			||||||
 | 
						if err = ctx.Session.Set("u2fChallenge", challenge); err != nil {
 | 
				
			||||||
 | 
							ctx.ServerError("UserSignIn", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ctx.JSON(200, challenge.SignRequest(regs.ToRegistrations()))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// U2FSign authenticates the user by signResp
 | 
				
			||||||
 | 
					func U2FSign(ctx *context.Context, signResp u2f.SignResponse) {
 | 
				
			||||||
 | 
						challSess := ctx.Session.Get("u2fChallenge")
 | 
				
			||||||
 | 
						idSess := ctx.Session.Get("twofaUid")
 | 
				
			||||||
 | 
						if challSess == nil || idSess == nil {
 | 
				
			||||||
 | 
							ctx.ServerError("UserSignIn", errors.New("not in U2F session"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						challenge := challSess.(*u2f.Challenge)
 | 
				
			||||||
 | 
						id := idSess.(int64)
 | 
				
			||||||
 | 
						regs, err := models.GetU2FRegistrationsByUID(id)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.ServerError("UserSignIn", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, reg := range regs {
 | 
				
			||||||
 | 
							r, err := reg.Parse()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Fatal(4, "parsing u2f registration: %v", err)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							newCounter, authErr := r.Authenticate(signResp, *challenge, reg.Counter)
 | 
				
			||||||
 | 
							if authErr == nil {
 | 
				
			||||||
 | 
								reg.Counter = newCounter
 | 
				
			||||||
 | 
								user, err := models.GetUserByID(id)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									ctx.ServerError("UserSignIn", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								remember := ctx.Session.Get("twofaRemember").(bool)
 | 
				
			||||||
 | 
								if err := reg.UpdateCounter(); err != nil {
 | 
				
			||||||
 | 
									ctx.ServerError("UserSignIn", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if ctx.Session.Get("linkAccount") != nil {
 | 
				
			||||||
 | 
									gothUser := ctx.Session.Get("linkAccountGothUser")
 | 
				
			||||||
 | 
									if gothUser == nil {
 | 
				
			||||||
 | 
										ctx.ServerError("UserSignIn", errors.New("not in LinkAccount session"))
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									err = models.LinkAccountToUser(user, gothUser.(goth.User))
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										ctx.ServerError("UserSignIn", err)
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								redirect := handleSignInFull(ctx, user, remember, false)
 | 
				
			||||||
 | 
								if redirect == "" {
 | 
				
			||||||
 | 
									redirect = setting.AppSubURL + "/"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								ctx.PlainText(200, []byte(redirect))
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ctx.Error(401)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// This handles the final part of the sign-in process of the user.
 | 
					// This handles the final part of the sign-in process of the user.
 | 
				
			||||||
func handleSignIn(ctx *context.Context, u *models.User, remember bool) {
 | 
					func handleSignIn(ctx *context.Context, u *models.User, remember bool) {
 | 
				
			||||||
	handleSignInFull(ctx, u, remember, true)
 | 
						handleSignInFull(ctx, u, remember, true)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyRedirect bool) {
 | 
					func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyRedirect bool) string {
 | 
				
			||||||
	if remember {
 | 
						if remember {
 | 
				
			||||||
		days := 86400 * setting.LogInRememberDays
 | 
							days := 86400 * setting.LogInRememberDays
 | 
				
			||||||
		ctx.SetCookie(setting.CookieUserName, u.Name, days, setting.AppSubURL)
 | 
							ctx.SetCookie(setting.CookieUserName, u.Name, days, setting.AppSubURL)
 | 
				
			||||||
@@ -336,6 +447,8 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR
 | 
				
			|||||||
	ctx.Session.Delete("openid_determined_username")
 | 
						ctx.Session.Delete("openid_determined_username")
 | 
				
			||||||
	ctx.Session.Delete("twofaUid")
 | 
						ctx.Session.Delete("twofaUid")
 | 
				
			||||||
	ctx.Session.Delete("twofaRemember")
 | 
						ctx.Session.Delete("twofaRemember")
 | 
				
			||||||
 | 
						ctx.Session.Delete("u2fChallenge")
 | 
				
			||||||
 | 
						ctx.Session.Delete("linkAccount")
 | 
				
			||||||
	ctx.Session.Set("uid", u.ID)
 | 
						ctx.Session.Set("uid", u.ID)
 | 
				
			||||||
	ctx.Session.Set("uname", u.Name)
 | 
						ctx.Session.Set("uname", u.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -345,7 +458,7 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR
 | 
				
			|||||||
		u.Language = ctx.Locale.Language()
 | 
							u.Language = ctx.Locale.Language()
 | 
				
			||||||
		if err := models.UpdateUserCols(u, "language"); err != nil {
 | 
							if err := models.UpdateUserCols(u, "language"); err != nil {
 | 
				
			||||||
			log.Error(4, fmt.Sprintf("Error updating user language [user: %d, locale: %s]", u.ID, u.Language))
 | 
								log.Error(4, fmt.Sprintf("Error updating user language [user: %d, locale: %s]", u.ID, u.Language))
 | 
				
			||||||
			return
 | 
								return setting.AppSubURL + "/"
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -358,7 +471,7 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR
 | 
				
			|||||||
	u.SetLastLogin()
 | 
						u.SetLastLogin()
 | 
				
			||||||
	if err := models.UpdateUserCols(u, "last_login_unix"); err != nil {
 | 
						if err := models.UpdateUserCols(u, "last_login_unix"); err != nil {
 | 
				
			||||||
		ctx.ServerError("UpdateUserCols", err)
 | 
							ctx.ServerError("UpdateUserCols", err)
 | 
				
			||||||
		return
 | 
							return setting.AppSubURL + "/"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 {
 | 
						if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 {
 | 
				
			||||||
@@ -366,12 +479,13 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR
 | 
				
			|||||||
		if obeyRedirect {
 | 
							if obeyRedirect {
 | 
				
			||||||
			ctx.RedirectToFirst(redirectTo)
 | 
								ctx.RedirectToFirst(redirectTo)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return
 | 
							return redirectTo
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if obeyRedirect {
 | 
						if obeyRedirect {
 | 
				
			||||||
		ctx.Redirect(setting.AppSubURL + "/")
 | 
							ctx.Redirect(setting.AppSubURL + "/")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						return setting.AppSubURL + "/"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SignInOAuth handles the OAuth2 login buttons
 | 
					// SignInOAuth handles the OAuth2 login buttons
 | 
				
			||||||
@@ -467,6 +581,14 @@ func handleOAuth2SignIn(u *models.User, gothUser goth.User, ctx *context.Context
 | 
				
			|||||||
	// User needs to use 2FA, save data and redirect to 2FA page.
 | 
						// User needs to use 2FA, save data and redirect to 2FA page.
 | 
				
			||||||
	ctx.Session.Set("twofaUid", u.ID)
 | 
						ctx.Session.Set("twofaUid", u.ID)
 | 
				
			||||||
	ctx.Session.Set("twofaRemember", false)
 | 
						ctx.Session.Set("twofaRemember", false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If U2F is enrolled -> Redirect to U2F instead
 | 
				
			||||||
 | 
						regs, err := models.GetU2FRegistrationsByUID(u.ID)
 | 
				
			||||||
 | 
						if err == nil && len(regs) > 0 {
 | 
				
			||||||
 | 
							ctx.Redirect(setting.AppSubURL + "/user/u2f")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.Redirect(setting.AppSubURL + "/user/two_factor")
 | 
						ctx.Redirect(setting.AppSubURL + "/user/two_factor")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -593,6 +715,13 @@ func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) {
 | 
				
			|||||||
	ctx.Session.Set("twofaRemember", signInForm.Remember)
 | 
						ctx.Session.Set("twofaRemember", signInForm.Remember)
 | 
				
			||||||
	ctx.Session.Set("linkAccount", true)
 | 
						ctx.Session.Set("linkAccount", true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If U2F is enrolled -> Redirect to U2F instead
 | 
				
			||||||
 | 
						regs, err := models.GetU2FRegistrationsByUID(u.ID)
 | 
				
			||||||
 | 
						if err == nil && len(regs) > 0 {
 | 
				
			||||||
 | 
							ctx.Redirect(setting.AppSubURL + "/user/u2f")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.Redirect(setting.AppSubURL + "/user/two_factor")
 | 
						ctx.Redirect(setting.AppSubURL + "/user/two_factor")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,6 +33,14 @@ func Security(ctx *context.Context) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ctx.Data["TwofaEnrolled"] = enrolled
 | 
						ctx.Data["TwofaEnrolled"] = enrolled
 | 
				
			||||||
 | 
						if enrolled {
 | 
				
			||||||
 | 
							ctx.Data["U2FRegistrations"], err = models.GetU2FRegistrationsByUID(ctx.User.ID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								ctx.ServerError("GetU2FRegistrationsByUID", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ctx.Data["RequireU2F"] = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tokens, err := models.ListAccessTokens(ctx.User.ID)
 | 
						tokens, err := models.ListAccessTokens(ctx.User.ID)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										99
									
								
								routers/user/setting/security_u2f.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								routers/user/setting/security_u2f.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,99 @@
 | 
				
			|||||||
 | 
					// Copyright 2018 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 setting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/auth"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/tstranex/u2f"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// U2FRegister initializes the u2f registration procedure
 | 
				
			||||||
 | 
					func U2FRegister(ctx *context.Context, form auth.U2FRegistrationForm) {
 | 
				
			||||||
 | 
						if form.Name == "" {
 | 
				
			||||||
 | 
							ctx.Error(409)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						challenge, err := u2f.NewChallenge(setting.U2F.AppID, setting.U2F.TrustedFacets)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.ServerError("NewChallenge", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = ctx.Session.Set("u2fChallenge", challenge)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.ServerError("Session.Set", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						regs, err := models.GetU2FRegistrationsByUID(ctx.User.ID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.ServerError("GetU2FRegistrationsByUID", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, reg := range regs {
 | 
				
			||||||
 | 
							if reg.Name == form.Name {
 | 
				
			||||||
 | 
								ctx.Error(409, "Name already taken")
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ctx.Session.Set("u2fName", form.Name)
 | 
				
			||||||
 | 
						ctx.JSON(200, u2f.NewWebRegisterRequest(challenge, regs.ToRegistrations()))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// U2FRegisterPost receives the response of the security key
 | 
				
			||||||
 | 
					func U2FRegisterPost(ctx *context.Context, response u2f.RegisterResponse) {
 | 
				
			||||||
 | 
						challSess := ctx.Session.Get("u2fChallenge")
 | 
				
			||||||
 | 
						u2fName := ctx.Session.Get("u2fName")
 | 
				
			||||||
 | 
						if challSess == nil || u2fName == nil {
 | 
				
			||||||
 | 
							ctx.ServerError("U2FRegisterPost", errors.New("not in U2F session"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						challenge := challSess.(*u2f.Challenge)
 | 
				
			||||||
 | 
						name := u2fName.(string)
 | 
				
			||||||
 | 
						config := &u2f.Config{
 | 
				
			||||||
 | 
							// Chrome 66+ doesn't return the device's attestation
 | 
				
			||||||
 | 
							// certificate by default.
 | 
				
			||||||
 | 
							SkipAttestationVerify: true,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						reg, err := u2f.Register(response, *challenge, config)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.ServerError("u2f.Register", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if _, err = models.CreateRegistration(ctx.User, name, reg); err != nil {
 | 
				
			||||||
 | 
							ctx.ServerError("u2f.Register", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ctx.Status(200)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// U2FDelete deletes an security key by id
 | 
				
			||||||
 | 
					func U2FDelete(ctx *context.Context, form auth.U2FDeleteForm) {
 | 
				
			||||||
 | 
						reg, err := models.GetU2FRegistrationByID(form.ID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if models.IsErrU2FRegistrationNotExist(err) {
 | 
				
			||||||
 | 
								ctx.Status(200)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ctx.ServerError("GetU2FRegistrationByID", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if reg.UserID != ctx.User.ID {
 | 
				
			||||||
 | 
							ctx.Status(401)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := models.DeleteRegistration(reg); err != nil {
 | 
				
			||||||
 | 
							ctx.ServerError("DeleteRegistration", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ctx.JSON(200, map[string]interface{}{
 | 
				
			||||||
 | 
							"redirect": setting.AppSubURL + "/user/settings/security",
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -64,6 +64,9 @@
 | 
				
			|||||||
{{if .RequireDropzone}}
 | 
					{{if .RequireDropzone}}
 | 
				
			||||||
	<script src="{{AppSubUrl}}/vendor/plugins/dropzone/dropzone.js"></script>
 | 
						<script src="{{AppSubUrl}}/vendor/plugins/dropzone/dropzone.js"></script>
 | 
				
			||||||
{{end}}
 | 
					{{end}}
 | 
				
			||||||
 | 
					{{if .RequireU2F}}
 | 
				
			||||||
 | 
						<script src="{{AppSubUrl}}/vendor/plugins/u2f/index.js"></script>
 | 
				
			||||||
 | 
					{{end}}
 | 
				
			||||||
{{if .RequireTribute}}
 | 
					{{if .RequireTribute}}
 | 
				
			||||||
	<script src="{{AppSubUrl}}/vendor/plugins/tribute/tribute.min.js"></script>
 | 
						<script src="{{AppSubUrl}}/vendor/plugins/tribute/tribute.min.js"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										22
									
								
								templates/user/auth/u2f.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								templates/user/auth/u2f.tmpl
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					{{template "base/head" .}}
 | 
				
			||||||
 | 
					<div class="user signin">
 | 
				
			||||||
 | 
						<div class="ui middle centered very relaxed page grid">
 | 
				
			||||||
 | 
							<div class="column">
 | 
				
			||||||
 | 
								<h3 class="ui top attached header">
 | 
				
			||||||
 | 
								{{.i18n.Tr "twofa"}}
 | 
				
			||||||
 | 
								</h3>
 | 
				
			||||||
 | 
								<div class="ui attached segment">
 | 
				
			||||||
 | 
									<i class="huge key icon"></i>
 | 
				
			||||||
 | 
									<h3>{{.i18n.Tr "u2f_insert_key"}}</h3>
 | 
				
			||||||
 | 
									{{template "base/alert" .}}
 | 
				
			||||||
 | 
									<p>{{.i18n.Tr "u2f_sign_in"}}</p>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
								<div id="wait-for-key" class="ui attached segment"><div class="ui active indeterminate inline loader"></div> {{.i18n.Tr "u2f_press_button"}} </div>
 | 
				
			||||||
 | 
								<div class="ui attached segment">
 | 
				
			||||||
 | 
									<a href="/user/two_factor">{{.i18n.Tr "u2f_use_twofa"}}</a>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{{template "user/auth/u2f_error" .}}
 | 
				
			||||||
 | 
					{{template "base/footer" .}}
 | 
				
			||||||
							
								
								
									
										32
									
								
								templates/user/auth/u2f_error.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								templates/user/auth/u2f_error.tmpl
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					<div class="ui small modal" id="u2f-error">
 | 
				
			||||||
 | 
						<div class="header">{{.i18n.Tr "u2f_error"}}</div>
 | 
				
			||||||
 | 
						<div class="content">
 | 
				
			||||||
 | 
							<div class="ui negative message">
 | 
				
			||||||
 | 
								<div class="header">
 | 
				
			||||||
 | 
								{{.i18n.Tr "u2f_error"}}
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
								<div class="hide" id="unsupported-browser">
 | 
				
			||||||
 | 
								{{.i18n.Tr "u2f_unsupported_browser"}}
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
								<div class="hide" id="u2f-error-1">
 | 
				
			||||||
 | 
								{{.i18n.Tr "u2f_error_1"}}
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
								<div class="hide" id="u2f-error-2">
 | 
				
			||||||
 | 
								{{.i18n.Tr "u2f_error_2"}}
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
								<div class="hide" id="u2f-error-3">
 | 
				
			||||||
 | 
								{{.i18n.Tr "u2f_error_3"}}
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
								<div class="hide" id="u2f-error-4">
 | 
				
			||||||
 | 
								{{.i18n.Tr "u2f_error_4"}}
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
								<div class="hide u2f-error-5">
 | 
				
			||||||
 | 
								{{.i18n.Tr "u2f_error_5"}}
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
						<div class="actions">
 | 
				
			||||||
 | 
							<button onclick="window.location.reload()" class="success ui button hide u2f_error_5">{{.i18n.Tr "u2f_reload"}}</button>
 | 
				
			||||||
 | 
							<div class="ui cancel button">{{.i18n.Tr "cancel"}}</div>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
@@ -4,6 +4,7 @@
 | 
				
			|||||||
	<div class="ui container">
 | 
						<div class="ui container">
 | 
				
			||||||
		{{template "base/alert" .}}
 | 
							{{template "base/alert" .}}
 | 
				
			||||||
		{{template "user/settings/security_twofa" .}}
 | 
							{{template "user/settings/security_twofa" .}}
 | 
				
			||||||
 | 
							{{template "user/settings/security_u2f" .}}
 | 
				
			||||||
		{{template "user/settings/security_accountlinks" .}}
 | 
							{{template "user/settings/security_accountlinks" .}}
 | 
				
			||||||
		{{if .EnableOpenIDSignIn}}
 | 
							{{if .EnableOpenIDSignIn}}
 | 
				
			||||||
		{{template "user/settings/security_openid" .}}
 | 
							{{template "user/settings/security_openid" .}}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,7 +43,7 @@
 | 
				
			|||||||
		{{.CsrfTokenHtml}}
 | 
							{{.CsrfTokenHtml}}
 | 
				
			||||||
		<div class="required field {{if .Err_OpenID}}error{{end}}">
 | 
							<div class="required field {{if .Err_OpenID}}error{{end}}">
 | 
				
			||||||
			<label for="openid">{{.i18n.Tr "settings.add_new_openid"}}</label>
 | 
								<label for="openid">{{.i18n.Tr "settings.add_new_openid"}}</label>
 | 
				
			||||||
			<input id="openid" name="openid" type="text" autofocus required>
 | 
								<input id="openid" name="openid" type="text" required>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<button class="ui green button">
 | 
							<button class="ui green button">
 | 
				
			||||||
			{{.i18n.Tr "settings.add_openid"}}
 | 
								{{.i18n.Tr "settings.add_openid"}}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										56
									
								
								templates/user/settings/security_u2f.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								templates/user/settings/security_u2f.tmpl
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					<h4 class="ui top attached header">
 | 
				
			||||||
 | 
					{{.i18n.Tr "settings.u2f"}}
 | 
				
			||||||
 | 
					</h4>
 | 
				
			||||||
 | 
					<div class="ui attached segment">
 | 
				
			||||||
 | 
						<p>{{.i18n.Tr "settings.u2f_desc" | Str2html}}</p>
 | 
				
			||||||
 | 
						{{if .TwofaEnrolled}}
 | 
				
			||||||
 | 
							<div class="ui key list">
 | 
				
			||||||
 | 
								{{range .U2FRegistrations}}
 | 
				
			||||||
 | 
								    <div class="item">
 | 
				
			||||||
 | 
								    	<div class="right floated content">
 | 
				
			||||||
 | 
								    		<button class="ui red tiny button delete-button" id="delete-registration" data-url="{{$.Link}}/u2f/delete" data-id="{{.ID}}">
 | 
				
			||||||
 | 
								    		{{$.i18n.Tr "settings.delete_key"}}
 | 
				
			||||||
 | 
								    		</button>
 | 
				
			||||||
 | 
								    	</div>
 | 
				
			||||||
 | 
								    	<div class="content">
 | 
				
			||||||
 | 
								    		<strong>{{.Name}}</strong>
 | 
				
			||||||
 | 
								    	</div>
 | 
				
			||||||
 | 
								    </div>
 | 
				
			||||||
 | 
								{{end}}
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
							<div class="ui form">
 | 
				
			||||||
 | 
								{{.CsrfTokenHtml}}
 | 
				
			||||||
 | 
								<div class="required field">
 | 
				
			||||||
 | 
									<label for="nickname">{{.i18n.Tr "settings.u2f_nickname"}}</label>
 | 
				
			||||||
 | 
									<input id="nickname" name="nickname" type="text" required>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
								<button id="register-security-key" class="positive ui labeled icon button"><i class="usb icon"></i>{{.i18n.Tr "settings.u2f_register_key"}}</button>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
						{{else}}
 | 
				
			||||||
 | 
							<b>{{.i18n.Tr "settings.u2f_require_twofa"}}</b>
 | 
				
			||||||
 | 
						{{end}}
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="ui small modal" id="register-device">
 | 
				
			||||||
 | 
						<div class="header">{{.i18n.Tr "settings.u2f_register_key"}}</div>
 | 
				
			||||||
 | 
						<div class="content">
 | 
				
			||||||
 | 
							<i class="notched spinner loading icon"></i> {{.i18n.Tr "settings.u2f_press_button"}}
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
						<div class="actions">
 | 
				
			||||||
 | 
							<div class="ui cancel button">{{.i18n.Tr "cancel"}}</div>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{{template "user/auth/u2f_error" .}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="ui small basic delete modal" id="delete-registration">
 | 
				
			||||||
 | 
						<div class="ui icon header">
 | 
				
			||||||
 | 
							<i class="trash icon"></i>
 | 
				
			||||||
 | 
						{{.i18n.Tr "settings.u2f_delete_key"}}
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
						<div class="content">
 | 
				
			||||||
 | 
							<p>{{.i18n.Tr "settings.u2f_delete_key_desc"}}</p>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
						{{template "base/delete_modal_actions" .}}
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										21
									
								
								vendor/github.com/tstranex/u2f/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/tstranex/u2f/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					The MIT License (MIT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Copyright (c) 2015 The Go FIDO U2F Library Authors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The above copyright notice and this permission notice shall be included in
 | 
				
			||||||
 | 
					all copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
				
			||||||
 | 
					THE SOFTWARE.
 | 
				
			||||||
							
								
								
									
										97
									
								
								vendor/github.com/tstranex/u2f/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								vendor/github.com/tstranex/u2f/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
				
			|||||||
 | 
					# Go FIDO U2F Library
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This Go package implements the parts of the FIDO U2F specification required on
 | 
				
			||||||
 | 
					the server side of an application.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[](https://travis-ci.org/tstranex/u2f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Native Go implementation
 | 
				
			||||||
 | 
					- No dependancies other than the Go standard library
 | 
				
			||||||
 | 
					- Token attestation certificate verification
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Usage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Please visit http://godoc.org/github.com/tstranex/u2f for the full
 | 
				
			||||||
 | 
					documentation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### How to enrol a new token
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					app_id := "http://localhost"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Send registration request to the browser.
 | 
				
			||||||
 | 
					c, _ := NewChallenge(app_id, []string{app_id})
 | 
				
			||||||
 | 
					req, _ := c.RegisterRequest()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Read response from the browser.
 | 
				
			||||||
 | 
					var resp RegisterResponse
 | 
				
			||||||
 | 
					reg, err := Register(resp, c, nil)
 | 
				
			||||||
 | 
					if err != nil {
 | 
				
			||||||
 | 
					    // Registration failed.
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Store registration in the database.
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### How to perform an authentication
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					// Fetch registration and counter from the database.
 | 
				
			||||||
 | 
					var reg Registration
 | 
				
			||||||
 | 
					var counter uint32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Send authentication request to the browser.
 | 
				
			||||||
 | 
					c, _ := NewChallenge(app_id, []string{app_id})
 | 
				
			||||||
 | 
					req, _ := c.SignRequest(reg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Read response from the browser.
 | 
				
			||||||
 | 
					var resp SignResponse
 | 
				
			||||||
 | 
					newCounter, err := reg.Authenticate(resp, c, counter)
 | 
				
			||||||
 | 
					if err != nil {
 | 
				
			||||||
 | 
					    // Authentication failed.
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Store updated counter in the database.
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Installation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					$ go get github.com/tstranex/u2f
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Example
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					See u2fdemo/main.go for an full example server. To run it:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					$ go install github.com/tstranex/u2f/u2fdemo
 | 
				
			||||||
 | 
					$ ./bin/u2fdemo
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Open https://localhost:3483 in Chrome.
 | 
				
			||||||
 | 
					Ignore the SSL warning (due to the self-signed certificate for localhost).
 | 
				
			||||||
 | 
					You can then test registering and authenticating using your token.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Changelog
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- 2016-12-18: The package has been updated to work with the new
 | 
				
			||||||
 | 
					  U2F Javascript 1.1 API specification. This causes some breaking changes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  `SignRequest` has been replaced by `WebSignRequest` which now includes
 | 
				
			||||||
 | 
					  multiple registrations. This is useful when the user has multiple devices
 | 
				
			||||||
 | 
					  registered since you can now authenticate against any of them with a single
 | 
				
			||||||
 | 
					  request.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  `WebRegisterRequest` has been introduced, which should generally be used
 | 
				
			||||||
 | 
					  instead of using `RegisterRequest` directly. It includes the list of existing
 | 
				
			||||||
 | 
					  registrations with the new registration request. If the user's device already
 | 
				
			||||||
 | 
					  matches one of the existing registrations, it will refuse to re-register.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  `Challenge.RegisterRequest` has been replaced by `NewWebRegisterRequest`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The Go FIDO U2F Library is licensed under the MIT License.
 | 
				
			||||||
							
								
								
									
										136
									
								
								vendor/github.com/tstranex/u2f/auth.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								vendor/github.com/tstranex/u2f/auth.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,136 @@
 | 
				
			|||||||
 | 
					// Go FIDO U2F Library
 | 
				
			||||||
 | 
					// Copyright 2015 The Go FIDO U2F Library Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by the MIT
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package u2f
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/ecdsa"
 | 
				
			||||||
 | 
						"crypto/sha256"
 | 
				
			||||||
 | 
						"encoding/asn1"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"math/big"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SignRequest creates a request to initiate an authentication.
 | 
				
			||||||
 | 
					func (c *Challenge) SignRequest(regs []Registration) *WebSignRequest {
 | 
				
			||||||
 | 
						var sr WebSignRequest
 | 
				
			||||||
 | 
						sr.AppID = c.AppID
 | 
				
			||||||
 | 
						sr.Challenge = encodeBase64(c.Challenge)
 | 
				
			||||||
 | 
						for _, r := range regs {
 | 
				
			||||||
 | 
							rk := getRegisteredKey(c.AppID, r)
 | 
				
			||||||
 | 
							sr.RegisteredKeys = append(sr.RegisteredKeys, rk)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &sr
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ErrCounterTooLow is raised when the counter value received from the device is
 | 
				
			||||||
 | 
					// lower than last stored counter value. This may indicate that the device has
 | 
				
			||||||
 | 
					// been cloned (or is malfunctioning). The application may choose to disable
 | 
				
			||||||
 | 
					// the particular device as precaution.
 | 
				
			||||||
 | 
					var ErrCounterTooLow = errors.New("u2f: counter too low")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Authenticate validates a SignResponse authentication response.
 | 
				
			||||||
 | 
					// An error is returned if any part of the response fails to validate.
 | 
				
			||||||
 | 
					// The counter should be the counter associated with appropriate device
 | 
				
			||||||
 | 
					// (i.e. resp.KeyHandle).
 | 
				
			||||||
 | 
					// The latest counter value is returned, which the caller should store.
 | 
				
			||||||
 | 
					func (reg *Registration) Authenticate(resp SignResponse, c Challenge, counter uint32) (newCounter uint32, err error) {
 | 
				
			||||||
 | 
						if time.Now().Sub(c.Timestamp) > timeout {
 | 
				
			||||||
 | 
							return 0, errors.New("u2f: challenge has expired")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if resp.KeyHandle != encodeBase64(reg.KeyHandle) {
 | 
				
			||||||
 | 
							return 0, errors.New("u2f: wrong key handle")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sigData, err := decodeBase64(resp.SignatureData)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						clientData, err := decodeBase64(resp.ClientData)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ar, err := parseSignResponse(sigData)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ar.Counter < counter {
 | 
				
			||||||
 | 
							return 0, ErrCounterTooLow
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := verifyClientData(clientData, c); err != nil {
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := verifyAuthSignature(*ar, ®.PubKey, c.AppID, clientData); err != nil {
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !ar.UserPresenceVerified {
 | 
				
			||||||
 | 
							return 0, errors.New("u2f: user was not present")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ar.Counter, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ecdsaSig struct {
 | 
				
			||||||
 | 
						R, S *big.Int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type authResp struct {
 | 
				
			||||||
 | 
						UserPresenceVerified bool
 | 
				
			||||||
 | 
						Counter              uint32
 | 
				
			||||||
 | 
						sig                  ecdsaSig
 | 
				
			||||||
 | 
						raw                  []byte
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func parseSignResponse(sd []byte) (*authResp, error) {
 | 
				
			||||||
 | 
						if len(sd) < 5 {
 | 
				
			||||||
 | 
							return nil, errors.New("u2f: data is too short")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var ar authResp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						userPresence := sd[0]
 | 
				
			||||||
 | 
						if userPresence|1 != 1 {
 | 
				
			||||||
 | 
							return nil, errors.New("u2f: invalid user presence byte")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ar.UserPresenceVerified = userPresence == 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ar.Counter = uint32(sd[1])<<24 | uint32(sd[2])<<16 | uint32(sd[3])<<8 | uint32(sd[4])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ar.raw = sd[:5]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rest, err := asn1.Unmarshal(sd[5:], &ar.sig)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(rest) != 0 {
 | 
				
			||||||
 | 
							return nil, errors.New("u2f: trailing data")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &ar, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func verifyAuthSignature(ar authResp, pubKey *ecdsa.PublicKey, appID string, clientData []byte) error {
 | 
				
			||||||
 | 
						appParam := sha256.Sum256([]byte(appID))
 | 
				
			||||||
 | 
						challenge := sha256.Sum256(clientData)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var buf []byte
 | 
				
			||||||
 | 
						buf = append(buf, appParam[:]...)
 | 
				
			||||||
 | 
						buf = append(buf, ar.raw...)
 | 
				
			||||||
 | 
						buf = append(buf, challenge[:]...)
 | 
				
			||||||
 | 
						hash := sha256.Sum256(buf)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !ecdsa.Verify(pubKey, hash[:], ar.sig.R, ar.sig.S) {
 | 
				
			||||||
 | 
							return errors.New("u2f: invalid signature")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										89
									
								
								vendor/github.com/tstranex/u2f/certs.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								vendor/github.com/tstranex/u2f/certs.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
				
			|||||||
 | 
					// Go FIDO U2F Library
 | 
				
			||||||
 | 
					// Copyright 2015 The Go FIDO U2F Library Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by the MIT
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package u2f
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/x509"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const plugUpCert = `-----BEGIN CERTIFICATE-----
 | 
				
			||||||
 | 
					MIIBrjCCAVSgAwIBAgIJAMGSvUZlGSGVMAoGCCqGSM49BAMCMDIxMDAuBgNVBAMM
 | 
				
			||||||
 | 
					J1BsdWctdXAgRklETyBJbnRlcm5hbCBBdHRlc3RhdGlvbiBDQSAjMTAeFw0xNDA5
 | 
				
			||||||
 | 
					MjMxNjM3NTFaFw0zNDA5MjMxNjM3NTFaMDIxMDAuBgNVBAMMJ1BsdWctdXAgRklE
 | 
				
			||||||
 | 
					TyBJbnRlcm5hbCBBdHRlc3RhdGlvbiBDQSAjMTBZMBMGByqGSM49AgEGCCqGSM49
 | 
				
			||||||
 | 
					AwEHA0IABH9mscDgEHo4AUh7J8JHqRxsSVxbvsbe6Pxy5cUFKfQlWNjxRrZcbhOb
 | 
				
			||||||
 | 
					UY3WsAwmKuUdOcghbpTILhdp8LG9z5GjUzBRMA8GA1UdEwEB/wQFMAMBAf8wHQYD
 | 
				
			||||||
 | 
					VR0OBBYEFM+nRPKhYlDwOemShePaUOd9sDqoMB8GA1UdIwQYMBaAFM+nRPKhYlDw
 | 
				
			||||||
 | 
					OemShePaUOd9sDqoMAoGCCqGSM49BAMCA0gAMEUCIQDVzqnX1rgvyJaZ7WZUm1ED
 | 
				
			||||||
 | 
					hJKSsDxRXEnH+/voqpq/zgIgH4RUR6vr9YNrkzuCq5R07gF7P4qhtg/4jy+dhl7o
 | 
				
			||||||
 | 
					NAU=
 | 
				
			||||||
 | 
					-----END CERTIFICATE-----
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const neowaveCert = `-----BEGIN CERTIFICATE-----
 | 
				
			||||||
 | 
					MIICJDCCAcugAwIBAgIJAIo+0R9DGvSBMAoGCCqGSM49BAMCMG8xCzAJBgNVBAYT
 | 
				
			||||||
 | 
					AkZSMQ8wDQYDVQQIDAZGcmFuY2UxETAPBgNVBAcMCEdhcmRhbm5lMRAwDgYDVQQK
 | 
				
			||||||
 | 
					DAdOZW93YXZlMSowKAYDVQQDDCFOZW93YXZlIEtFWURPIEZJRE8gVTJGIENBIEJh
 | 
				
			||||||
 | 
					dGNoIDEwHhcNMTUwMTI4MTA1ODM1WhcNMjUwMTI1MTA1ODM1WjBvMQswCQYDVQQG
 | 
				
			||||||
 | 
					EwJGUjEPMA0GA1UECAwGRnJhbmNlMREwDwYDVQQHDAhHYXJkYW5uZTEQMA4GA1UE
 | 
				
			||||||
 | 
					CgwHTmVvd2F2ZTEqMCgGA1UEAwwhTmVvd2F2ZSBLRVlETyBGSURPIFUyRiBDQSBC
 | 
				
			||||||
 | 
					YXRjaCAxMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBlUmE1BRE/M/CE/ZCN+x
 | 
				
			||||||
 | 
					eutfnVsThMwIDN+4DL9gqXoKCeRMiDQ1zwm/yQS80BYSEz7Du9RU+2mlnyhwhu+f
 | 
				
			||||||
 | 
					BqNQME4wHQYDVR0OBBYEFF42te8/iq5HGom4sIhgkJWLq5jkMB8GA1UdIwQYMBaA
 | 
				
			||||||
 | 
					FF42te8/iq5HGom4sIhgkJWLq5jkMAwGA1UdEwQFMAMBAf8wCgYIKoZIzj0EAwID
 | 
				
			||||||
 | 
					RwAwRAIgVTxBFb2Hclq5Yi5gQp6WoZAcHETfKASvTQVOE88REGQCIA5DcwGVLsZB
 | 
				
			||||||
 | 
					QTb94Xgtb/WUieCvmwukFl/gEO15f3uA
 | 
				
			||||||
 | 
					-----END CERTIFICATE-----
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const yubicoRootCert = `-----BEGIN CERTIFICATE-----
 | 
				
			||||||
 | 
					MIIDHjCCAgagAwIBAgIEG0BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ
 | 
				
			||||||
 | 
					dWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw
 | 
				
			||||||
 | 
					MDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290
 | 
				
			||||||
 | 
					IENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
 | 
				
			||||||
 | 
					AoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk
 | 
				
			||||||
 | 
					5N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep
 | 
				
			||||||
 | 
					8EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbw
 | 
				
			||||||
 | 
					nebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT
 | 
				
			||||||
 | 
					9nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXw
 | 
				
			||||||
 | 
					LvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJ
 | 
				
			||||||
 | 
					hjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAN
 | 
				
			||||||
 | 
					BgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4
 | 
				
			||||||
 | 
					MYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kt
 | 
				
			||||||
 | 
					hX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2k
 | 
				
			||||||
 | 
					LVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1U
 | 
				
			||||||
 | 
					sG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqc
 | 
				
			||||||
 | 
					U9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw==
 | 
				
			||||||
 | 
					-----END CERTIFICATE-----
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const entersektCert = `-----BEGIN CERTIFICATE-----
 | 
				
			||||||
 | 
					MIICHjCCAcOgAwIBAgIBADAKBggqhkjOPQQDAjBvMQswCQYDVQQGEwJaQTEVMBMG
 | 
				
			||||||
 | 
					A1UECAwMV2VzdGVybiBDYXBlMRUwEwYDVQQHDAxTdGVsbGVuYm9zY2gxEjAQBgNV
 | 
				
			||||||
 | 
					BAoMCUVudGVyc2VrdDELMAkGA1UECwwCSVQxETAPBgNVBAMMCFRyYW5zYWt0MB4X
 | 
				
			||||||
 | 
					DTE0MTEwMTExMjczNFoXDTE1MTEwMTExMjczNFowbzELMAkGA1UEBhMCWkExFTAT
 | 
				
			||||||
 | 
					BgNVBAgMDFdlc3Rlcm4gQ2FwZTEVMBMGA1UEBwwMU3RlbGxlbmJvc2NoMRIwEAYD
 | 
				
			||||||
 | 
					VQQKDAlFbnRlcnNla3QxCzAJBgNVBAsMAklUMREwDwYDVQQDDAhUcmFuc2FrdDBZ
 | 
				
			||||||
 | 
					MBMGByqGSM49AgEGCCqGSM49AwEHA0IABBh10blFheMZy3k2iqW9TzLhS1DbJ/Xf
 | 
				
			||||||
 | 
					DxqQJJkpqTLq7vI+K3O4C20YtN0jsVrj7UylWoSRlPL5F7IkbeQ6aZ6jUDBOMB0G
 | 
				
			||||||
 | 
					A1UdDgQWBBQWRFF7mVAipWTdfBWk2B8Dv4Ab4jAfBgNVHSMEGDAWgBQWRFF7mVAi
 | 
				
			||||||
 | 
					pWTdfBWk2B8Dv4Ab4jAMBgNVHRMEBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQCo
 | 
				
			||||||
 | 
					bMURXOxv6pqz6ECBh0zgL2vVhEfTOZJOW0PACGalWgIhAME0LHGi6ZS7z9yzHNqi
 | 
				
			||||||
 | 
					cnRb+okM+PIy/hBcBuqTWCbw
 | 
				
			||||||
 | 
					-----END CERTIFICATE-----
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func mustLoadPool(pemCerts []byte) *x509.CertPool {
 | 
				
			||||||
 | 
						p := x509.NewCertPool()
 | 
				
			||||||
 | 
						if !p.AppendCertsFromPEM(pemCerts) {
 | 
				
			||||||
 | 
							log.Fatal("u2f: Error loading root cert pool.")
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return p
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var roots = mustLoadPool([]byte(yubicoRootCert + entersektCert + neowaveCert + plugUpCert))
 | 
				
			||||||
							
								
								
									
										87
									
								
								vendor/github.com/tstranex/u2f/messages.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								vendor/github.com/tstranex/u2f/messages.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
				
			|||||||
 | 
					// Go FIDO U2F Library
 | 
				
			||||||
 | 
					// Copyright 2015 The Go FIDO U2F Library Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by the MIT
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package u2f
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// JwkKey represents a public key used by a browser for the Channel ID TLS
 | 
				
			||||||
 | 
					// extension.
 | 
				
			||||||
 | 
					type JwkKey struct {
 | 
				
			||||||
 | 
						KTy string `json:"kty"`
 | 
				
			||||||
 | 
						Crv string `json:"crv"`
 | 
				
			||||||
 | 
						X   string `json:"x"`
 | 
				
			||||||
 | 
						Y   string `json:"y"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ClientData as defined by the FIDO U2F Raw Message Formats specification.
 | 
				
			||||||
 | 
					type ClientData struct {
 | 
				
			||||||
 | 
						Typ       string          `json:"typ"`
 | 
				
			||||||
 | 
						Challenge string          `json:"challenge"`
 | 
				
			||||||
 | 
						Origin    string          `json:"origin"`
 | 
				
			||||||
 | 
						CIDPubKey json.RawMessage `json:"cid_pubkey"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RegisterRequest as defined by the FIDO U2F Javascript API 1.1.
 | 
				
			||||||
 | 
					type RegisterRequest struct {
 | 
				
			||||||
 | 
						Version   string `json:"version"`
 | 
				
			||||||
 | 
						Challenge string `json:"challenge"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WebRegisterRequest contains the parameters needed for the u2f.register()
 | 
				
			||||||
 | 
					// high-level Javascript API function as defined by the
 | 
				
			||||||
 | 
					// FIDO U2F Javascript API 1.1.
 | 
				
			||||||
 | 
					type WebRegisterRequest struct {
 | 
				
			||||||
 | 
						AppID            string            `json:"appId"`
 | 
				
			||||||
 | 
						RegisterRequests []RegisterRequest `json:"registerRequests"`
 | 
				
			||||||
 | 
						RegisteredKeys   []RegisteredKey   `json:"registeredKeys"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RegisterResponse as defined by the FIDO U2F Javascript API 1.1.
 | 
				
			||||||
 | 
					type RegisterResponse struct {
 | 
				
			||||||
 | 
						Version          string `json:"version"`
 | 
				
			||||||
 | 
						RegistrationData string `json:"registrationData"`
 | 
				
			||||||
 | 
						ClientData       string `json:"clientData"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RegisteredKey as defined by the FIDO U2F Javascript API 1.1.
 | 
				
			||||||
 | 
					type RegisteredKey struct {
 | 
				
			||||||
 | 
						Version   string `json:"version"`
 | 
				
			||||||
 | 
						KeyHandle string `json:"keyHandle"`
 | 
				
			||||||
 | 
						AppID     string `json:"appId"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WebSignRequest contains the parameters needed for the u2f.sign()
 | 
				
			||||||
 | 
					// high-level Javascript API function as defined by the
 | 
				
			||||||
 | 
					// FIDO U2F Javascript API 1.1.
 | 
				
			||||||
 | 
					type WebSignRequest struct {
 | 
				
			||||||
 | 
						AppID          string          `json:"appId"`
 | 
				
			||||||
 | 
						Challenge      string          `json:"challenge"`
 | 
				
			||||||
 | 
						RegisteredKeys []RegisteredKey `json:"registeredKeys"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SignResponse as defined by the FIDO U2F Javascript API 1.1.
 | 
				
			||||||
 | 
					type SignResponse struct {
 | 
				
			||||||
 | 
						KeyHandle     string `json:"keyHandle"`
 | 
				
			||||||
 | 
						SignatureData string `json:"signatureData"`
 | 
				
			||||||
 | 
						ClientData    string `json:"clientData"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TrustedFacets as defined by the FIDO AppID and Facet Specification.
 | 
				
			||||||
 | 
					type TrustedFacets struct {
 | 
				
			||||||
 | 
						Version struct {
 | 
				
			||||||
 | 
							Major int `json:"major"`
 | 
				
			||||||
 | 
							Minor int `json:"minor"`
 | 
				
			||||||
 | 
						} `json:"version"`
 | 
				
			||||||
 | 
						Ids []string `json:"ids"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TrustedFacetsEndpoint is a container of TrustedFacets.
 | 
				
			||||||
 | 
					// It is used as the response for an appId URL endpoint.
 | 
				
			||||||
 | 
					type TrustedFacetsEndpoint struct {
 | 
				
			||||||
 | 
						TrustedFacets []TrustedFacets `json:"trustedFacets"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										230
									
								
								vendor/github.com/tstranex/u2f/register.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								vendor/github.com/tstranex/u2f/register.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,230 @@
 | 
				
			|||||||
 | 
					// Go FIDO U2F Library
 | 
				
			||||||
 | 
					// Copyright 2015 The Go FIDO U2F Library Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by the MIT
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package u2f
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/ecdsa"
 | 
				
			||||||
 | 
						"crypto/elliptic"
 | 
				
			||||||
 | 
						"crypto/sha256"
 | 
				
			||||||
 | 
						"crypto/x509"
 | 
				
			||||||
 | 
						"encoding/asn1"
 | 
				
			||||||
 | 
						"encoding/hex"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Registration represents a single enrolment or pairing between an
 | 
				
			||||||
 | 
					// application and a token. This data will typically be stored in a database.
 | 
				
			||||||
 | 
					type Registration struct {
 | 
				
			||||||
 | 
						// Raw serialized registration data as received from the token.
 | 
				
			||||||
 | 
						Raw []byte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						KeyHandle []byte
 | 
				
			||||||
 | 
						PubKey    ecdsa.PublicKey
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// AttestationCert can be nil for Authenticate requests.
 | 
				
			||||||
 | 
						AttestationCert *x509.Certificate
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Config contains configurable options for the package.
 | 
				
			||||||
 | 
					type Config struct {
 | 
				
			||||||
 | 
						// SkipAttestationVerify controls whether the token attestation
 | 
				
			||||||
 | 
						// certificate should be verified on registration. Ideally it should
 | 
				
			||||||
 | 
						// always be verified. However, there is currently no public list of
 | 
				
			||||||
 | 
						// trusted attestation root certificates so it may be necessary to skip.
 | 
				
			||||||
 | 
						SkipAttestationVerify bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// RootAttestationCertPool overrides the default root certificates used
 | 
				
			||||||
 | 
						// to verify client attestations. If nil, this defaults to the roots that are
 | 
				
			||||||
 | 
						// bundled in this library.
 | 
				
			||||||
 | 
						RootAttestationCertPool *x509.CertPool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Register validates a RegisterResponse message to enrol a new token.
 | 
				
			||||||
 | 
					// An error is returned if any part of the response fails to validate.
 | 
				
			||||||
 | 
					// The returned Registration should be stored by the caller.
 | 
				
			||||||
 | 
					func Register(resp RegisterResponse, c Challenge, config *Config) (*Registration, error) {
 | 
				
			||||||
 | 
						if config == nil {
 | 
				
			||||||
 | 
							config = &Config{}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if time.Now().Sub(c.Timestamp) > timeout {
 | 
				
			||||||
 | 
							return nil, errors.New("u2f: challenge has expired")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						regData, err := decodeBase64(resp.RegistrationData)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						clientData, err := decodeBase64(resp.ClientData)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						reg, sig, err := parseRegistration(regData)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := verifyClientData(clientData, c); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := verifyAttestationCert(*reg, config); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := verifyRegistrationSignature(*reg, sig, c.AppID, clientData); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return reg, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func parseRegistration(buf []byte) (*Registration, []byte, error) {
 | 
				
			||||||
 | 
						if len(buf) < 1+65+1+1+1 {
 | 
				
			||||||
 | 
							return nil, nil, errors.New("u2f: data is too short")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var r Registration
 | 
				
			||||||
 | 
						r.Raw = buf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if buf[0] != 0x05 {
 | 
				
			||||||
 | 
							return nil, nil, errors.New("u2f: invalid reserved byte")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						buf = buf[1:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						x, y := elliptic.Unmarshal(elliptic.P256(), buf[:65])
 | 
				
			||||||
 | 
						if x == nil {
 | 
				
			||||||
 | 
							return nil, nil, errors.New("u2f: invalid public key")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						r.PubKey.Curve = elliptic.P256()
 | 
				
			||||||
 | 
						r.PubKey.X = x
 | 
				
			||||||
 | 
						r.PubKey.Y = y
 | 
				
			||||||
 | 
						buf = buf[65:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						khLen := int(buf[0])
 | 
				
			||||||
 | 
						buf = buf[1:]
 | 
				
			||||||
 | 
						if len(buf) < khLen {
 | 
				
			||||||
 | 
							return nil, nil, errors.New("u2f: invalid key handle")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						r.KeyHandle = buf[:khLen]
 | 
				
			||||||
 | 
						buf = buf[khLen:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The length of the x509 cert isn't specified so it has to be inferred
 | 
				
			||||||
 | 
						// by parsing. We can't use x509.ParseCertificate yet because it returns
 | 
				
			||||||
 | 
						// an error if there are any trailing bytes. So parse raw asn1 as a
 | 
				
			||||||
 | 
						// workaround to get the length.
 | 
				
			||||||
 | 
						sig, err := asn1.Unmarshal(buf, &asn1.RawValue{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						buf = buf[:len(buf)-len(sig)]
 | 
				
			||||||
 | 
						fixCertIfNeed(buf)
 | 
				
			||||||
 | 
						cert, err := x509.ParseCertificate(buf)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						r.AttestationCert = cert
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &r, sig, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UnmarshalBinary implements encoding.BinaryMarshaler.
 | 
				
			||||||
 | 
					func (r *Registration) UnmarshalBinary(data []byte) error {
 | 
				
			||||||
 | 
						reg, _, err := parseRegistration(data)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						*r = *reg
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MarshalBinary implements encoding.BinaryUnmarshaler.
 | 
				
			||||||
 | 
					func (r *Registration) MarshalBinary() ([]byte, error) {
 | 
				
			||||||
 | 
						return r.Raw, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func verifyAttestationCert(r Registration, config *Config) error {
 | 
				
			||||||
 | 
						if config.SkipAttestationVerify {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						rootCertPool := roots
 | 
				
			||||||
 | 
						if config.RootAttestationCertPool != nil {
 | 
				
			||||||
 | 
							rootCertPool = config.RootAttestationCertPool
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						opts := x509.VerifyOptions{Roots: rootCertPool}
 | 
				
			||||||
 | 
						_, err := r.AttestationCert.Verify(opts)
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func verifyRegistrationSignature(
 | 
				
			||||||
 | 
						r Registration, signature []byte, appid string, clientData []byte) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						appParam := sha256.Sum256([]byte(appid))
 | 
				
			||||||
 | 
						challenge := sha256.Sum256(clientData)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						buf := []byte{0}
 | 
				
			||||||
 | 
						buf = append(buf, appParam[:]...)
 | 
				
			||||||
 | 
						buf = append(buf, challenge[:]...)
 | 
				
			||||||
 | 
						buf = append(buf, r.KeyHandle...)
 | 
				
			||||||
 | 
						pk := elliptic.Marshal(r.PubKey.Curve, r.PubKey.X, r.PubKey.Y)
 | 
				
			||||||
 | 
						buf = append(buf, pk...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return r.AttestationCert.CheckSignature(
 | 
				
			||||||
 | 
							x509.ECDSAWithSHA256, buf, signature)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getRegisteredKey(appID string, r Registration) RegisteredKey {
 | 
				
			||||||
 | 
						return RegisteredKey{
 | 
				
			||||||
 | 
							Version:   u2fVersion,
 | 
				
			||||||
 | 
							KeyHandle: encodeBase64(r.KeyHandle),
 | 
				
			||||||
 | 
							AppID:     appID,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// fixCertIfNeed fixes broken certificates described in
 | 
				
			||||||
 | 
					// https://github.com/Yubico/php-u2flib-server/blob/master/src/u2flib_server/U2F.php#L84
 | 
				
			||||||
 | 
					func fixCertIfNeed(cert []byte) {
 | 
				
			||||||
 | 
						h := sha256.Sum256(cert)
 | 
				
			||||||
 | 
						switch hex.EncodeToString(h[:]) {
 | 
				
			||||||
 | 
						case
 | 
				
			||||||
 | 
							"349bca1031f8c82c4ceca38b9cebf1a69df9fb3b94eed99eb3fb9aa3822d26e8",
 | 
				
			||||||
 | 
							"dd574527df608e47ae45fbba75a2afdd5c20fd94a02419381813cd55a2a3398f",
 | 
				
			||||||
 | 
							"1d8764f0f7cd1352df6150045c8f638e517270e8b5dda1c63ade9c2280240cae",
 | 
				
			||||||
 | 
							"d0edc9a91a1677435a953390865d208c55b3183c6759c9b5a7ff494c322558eb",
 | 
				
			||||||
 | 
							"6073c436dcd064a48127ddbf6032ac1a66fd59a0c24434f070d4e564c124c897",
 | 
				
			||||||
 | 
							"ca993121846c464d666096d35f13bf44c1b05af205f9b4a1e00cf6cc10c5e511":
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// clear the offending byte.
 | 
				
			||||||
 | 
							cert[len(cert)-257] = 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewWebRegisterRequest creates a request to enrol a new token.
 | 
				
			||||||
 | 
					// regs is the list of the user's existing registration. The browser will
 | 
				
			||||||
 | 
					// refuse to re-register a device if it has an existing registration.
 | 
				
			||||||
 | 
					func NewWebRegisterRequest(c *Challenge, regs []Registration) *WebRegisterRequest {
 | 
				
			||||||
 | 
						req := RegisterRequest{
 | 
				
			||||||
 | 
							Version:   u2fVersion,
 | 
				
			||||||
 | 
							Challenge: encodeBase64(c.Challenge),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rr := WebRegisterRequest{
 | 
				
			||||||
 | 
							AppID:            c.AppID,
 | 
				
			||||||
 | 
							RegisterRequests: []RegisterRequest{req},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, r := range regs {
 | 
				
			||||||
 | 
							rk := getRegisteredKey(c.AppID, r)
 | 
				
			||||||
 | 
							rr.RegisteredKeys = append(rr.RegisteredKeys, rk)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &rr
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										125
									
								
								vendor/github.com/tstranex/u2f/util.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								vendor/github.com/tstranex/u2f/util.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,125 @@
 | 
				
			|||||||
 | 
					// Go FIDO U2F Library
 | 
				
			||||||
 | 
					// Copyright 2015 The Go FIDO U2F Library Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by the MIT
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					Package u2f implements the server-side parts of the
 | 
				
			||||||
 | 
					FIDO Universal 2nd Factor (U2F) specification.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Applications will usually persist Challenge and Registration objects in a
 | 
				
			||||||
 | 
					database.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To enrol a new token:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    app_id := "http://localhost"
 | 
				
			||||||
 | 
					    c, _ := NewChallenge(app_id, []string{app_id})
 | 
				
			||||||
 | 
					    req, _ := u2f.NewWebRegisterRequest(c, existingTokens)
 | 
				
			||||||
 | 
					    // Send the request to the browser.
 | 
				
			||||||
 | 
					    var resp RegisterResponse
 | 
				
			||||||
 | 
					    // Read resp from the browser.
 | 
				
			||||||
 | 
					    reg, err := Register(resp, c)
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					         // Registration failed.
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // Store reg in the database.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To perform an authentication:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var regs []Registration
 | 
				
			||||||
 | 
					    // Fetch regs from the database.
 | 
				
			||||||
 | 
					    c, _ := NewChallenge(app_id, []string{app_id})
 | 
				
			||||||
 | 
					    req, _ := c.SignRequest(regs)
 | 
				
			||||||
 | 
					    // Send the request to the browser.
 | 
				
			||||||
 | 
					    var resp SignResponse
 | 
				
			||||||
 | 
					    // Read resp from the browser.
 | 
				
			||||||
 | 
					    new_counter, err := reg.Authenticate(resp, c)
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					        // Authentication failed.
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    reg.Counter = new_counter
 | 
				
			||||||
 | 
					    // Store updated Registration in the database.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The FIDO U2F specification can be found here:
 | 
				
			||||||
 | 
					https://fidoalliance.org/specifications/download
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					package u2f
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/rand"
 | 
				
			||||||
 | 
						"crypto/subtle"
 | 
				
			||||||
 | 
						"encoding/base64"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const u2fVersion = "U2F_V2"
 | 
				
			||||||
 | 
					const timeout = 5 * time.Minute
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func decodeBase64(s string) ([]byte, error) {
 | 
				
			||||||
 | 
						for i := 0; i < len(s)%4; i++ {
 | 
				
			||||||
 | 
							s += "="
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return base64.URLEncoding.DecodeString(s)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func encodeBase64(buf []byte) string {
 | 
				
			||||||
 | 
						s := base64.URLEncoding.EncodeToString(buf)
 | 
				
			||||||
 | 
						return strings.TrimRight(s, "=")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Challenge represents a single transaction between the server and
 | 
				
			||||||
 | 
					// authenticator. This data will typically be stored in a database.
 | 
				
			||||||
 | 
					type Challenge struct {
 | 
				
			||||||
 | 
						Challenge     []byte
 | 
				
			||||||
 | 
						Timestamp     time.Time
 | 
				
			||||||
 | 
						AppID         string
 | 
				
			||||||
 | 
						TrustedFacets []string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewChallenge generates a challenge for the given application.
 | 
				
			||||||
 | 
					func NewChallenge(appID string, trustedFacets []string) (*Challenge, error) {
 | 
				
			||||||
 | 
						challenge := make([]byte, 32)
 | 
				
			||||||
 | 
						n, err := rand.Read(challenge)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if n != 32 {
 | 
				
			||||||
 | 
							return nil, errors.New("u2f: unable to generate random bytes")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var c Challenge
 | 
				
			||||||
 | 
						c.Challenge = challenge
 | 
				
			||||||
 | 
						c.Timestamp = time.Now()
 | 
				
			||||||
 | 
						c.AppID = appID
 | 
				
			||||||
 | 
						c.TrustedFacets = trustedFacets
 | 
				
			||||||
 | 
						return &c, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func verifyClientData(clientData []byte, challenge Challenge) error {
 | 
				
			||||||
 | 
						var cd ClientData
 | 
				
			||||||
 | 
						if err := json.Unmarshal(clientData, &cd); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						foundFacetID := false
 | 
				
			||||||
 | 
						for _, facetID := range challenge.TrustedFacets {
 | 
				
			||||||
 | 
							if facetID == cd.Origin {
 | 
				
			||||||
 | 
								foundFacetID = true
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !foundFacetID {
 | 
				
			||||||
 | 
							return errors.New("u2f: untrusted facet id")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c := encodeBase64(challenge.Challenge)
 | 
				
			||||||
 | 
						if len(c) != len(cd.Challenge) ||
 | 
				
			||||||
 | 
							subtle.ConstantTimeCompare([]byte(c), []byte(cd.Challenge)) != 1 {
 | 
				
			||||||
 | 
							return errors.New("u2f: challenge does not match")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							@@ -1368,6 +1368,12 @@
 | 
				
			|||||||
			"revision": "917f41c560270110ceb73c5b38be2a9127387071",
 | 
								"revision": "917f41c560270110ceb73c5b38be2a9127387071",
 | 
				
			||||||
			"revisionTime": "2016-03-11T05:04:36Z"
 | 
								"revisionTime": "2016-03-11T05:04:36Z"
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"checksumSHA1": "NE1kNfAZ0AAXCUbwx196os/DSUE=",
 | 
				
			||||||
 | 
								"path": "github.com/tstranex/u2f",
 | 
				
			||||||
 | 
								"revision": "d21a03e0b1d9fc1df59ff54e7a513655c1748b0c",
 | 
				
			||||||
 | 
								"revisionTime": "2018-05-05T18:51:14Z"
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			"checksumSHA1": "MfWqWj0xRPdk1DpXCN0EXyBCa4Q=",
 | 
								"checksumSHA1": "MfWqWj0xRPdk1DpXCN0EXyBCa4Q=",
 | 
				
			||||||
			"path": "github.com/tinylib/msgp/msgp",
 | 
								"path": "github.com/tinylib/msgp/msgp",
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user