1
0
Fork 0
forked from forgejo/forgejo

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:
Jonas Franz 2018-05-19 16:12:37 +02:00 committed by Lauris BH
parent f933bcdfee
commit 951309f76a
34 changed files with 1599 additions and 9 deletions

21
vendor/github.com/tstranex/u2f/LICENSE generated vendored Normal file
View 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
View 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.
[![Build Status](https://travis-ci.org/tstranex/u2f.svg?branch=master)](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
View 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, &reg.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
View 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
View 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
View 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
View 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
}