mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09: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:
		
							
								
								
									
										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 | ||||
| } | ||||
		Reference in New Issue
	
	Block a user