Commit 079c7ff5 authored by ale's avatar ale

Use the unified composite type package from ai3/go-common

Avoid implementing our own version of asp/userenckey/u2f serialization
and deserialization.
parent 35d4d778
Pipeline #2558 passed with stages
in 4 minutes and 22 seconds
package backend
import (
"bytes"
"context"
"errors"
"fmt"
......@@ -9,6 +8,7 @@ import (
"strings"
ldaputil "git.autistici.org/ai3/go-common/ldap"
ct "git.autistici.org/ai3/go-common/ldap/compositetypes"
"gopkg.in/ldap.v3"
)
......@@ -136,11 +136,12 @@ func NewLDAPBackend(config *LDAPConfig) (*ldapBackend, error) {
// The encrypted private keys are a compound object in LDAP (in
// "id:key" format), we can safely ignore the key id here.
func decodePrivateKey(enc []byte) []byte {
if n := bytes.IndexByte(enc, ':'); n >= 0 {
return enc[n+1:]
func decodePrivateKey(enc string) ([]byte, error) {
key, err := ct.UnmarshalEncryptedKey(enc)
if err != nil {
return nil, err
}
return enc
return key.EncryptedKey, nil
}
func (b *ldapBackend) GetPrivateKeys(ctx context.Context, username string) ([][]byte, error) {
......@@ -152,7 +153,9 @@ func (b *ldapBackend) GetPrivateKeys(ctx context.Context, username string) ([][]
var out [][]byte
for _, ent := range result.Entries {
for _, val := range ent.GetAttributeValues(b.config.Query.PrivateKeyAttr) {
out = append(out, decodePrivateKey([]byte(val)))
if key, err := decodePrivateKey(val); err == nil {
out = append(out, key)
}
}
}
return out, nil
......
// Package compositetypes provides Go types for the composite values
// stored in our LDAP database, so that various authentication
// packages can agree on their serialized representation.
//
// These are normally 1-to-many associations that are wrapped into
// repeated LDAP attributes instead of separate nested objects, for
// simplicity and latency reasons.
//
// Whenever there is an 'id' field, it's a unique (per-user)
// identifier used to recognize a specific entry on modify/delete.
//
// The serialized values can be arbitrary []byte sequences (the LDAP
// schema should specify the right types for the associated
// attributes).
//
package compositetypes
import (
"crypto/elliptic"
"errors"
"strings"
"github.com/tstranex/u2f"
)
// AppSpecificPassword stores information on an application-specific
// password.
//
// Serialized as colon-separated fields with the format:
//
// id:service:encrypted_password:comment
//
// Where 'comment' is free-form and can contain colons, no escaping is
// performed.
type AppSpecificPassword struct {
ID string `json:"id"`
Service string `json:"service"`
EncryptedPassword string `json:"encrypted_password"`
Comment string `json:"comment"`
}
// Marshal returns the serialized format.
func (p *AppSpecificPassword) Marshal() string {
return strings.Join([]string{
p.ID,
p.Service,
p.EncryptedPassword,
p.Comment,
}, ":")
}
// UnmarshalAppSpecificPassword parses a serialized representation of
// an AppSpecificPassword.
func UnmarshalAppSpecificPassword(s string) (*AppSpecificPassword, error) {
parts := strings.SplitN(s, ":", 4)
if len(parts) != 4 {
return nil, errors.New("badly encoded app-specific password")
}
return &AppSpecificPassword{
ID: parts[0],
Service: parts[1],
EncryptedPassword: parts[2],
Comment: parts[3],
}, nil
}
// EncryptedKey stores a password-encrypted secret key.
//
// Serialized as colon-separated fields with the format:
//
// id:encrypted_key
//
// The encrypted key is stored as a raw, unencoded byte sequence.
type EncryptedKey struct {
ID string `json:"id"`
EncryptedKey []byte `json:"encrypted_key"`
}
// Marshal returns the serialized format.
func (k *EncryptedKey) Marshal() string {
var b []byte
b = append(b, []byte(k.ID)...)
b = append(b, ':')
b = append(b, k.EncryptedKey...)
return string(b)
}
// UnmarshalEncryptedKey parses the serialized representation of an
// EncryptedKey.
func UnmarshalEncryptedKey(s string) (*EncryptedKey, error) {
idx := strings.IndexByte(s, ':')
if idx < 0 {
return nil, errors.New("badly encoded key")
}
return &EncryptedKey{
ID: s[:idx],
EncryptedKey: []byte(s[idx+1:]),
}, nil
}
// U2FRegistration stores information on a single U2F device
// registration.
//
// The serialized format follows part of the U2F standard and just
// stores 64 bytes of the public key immediately followed by the key
// handle data, with no encoding.
//
// The data in U2FRegistration is still encoded, but it can be turned
// into a usable form (github.com/tstranex/u2f.Registration) later.
type U2FRegistration struct {
KeyHandle []byte `json:"key_handle"`
PublicKey []byte `json:"public_key"`
}
// Marshal returns the serialized format.
func (r *U2FRegistration) Marshal() string {
var b []byte
b = append(b, r.PublicKey...)
b = append(b, r.KeyHandle...)
return string(b)
}
// UnmarshalU2FRegistration parses a U2FRegistration from its serialized format.
func UnmarshalU2FRegistration(s string) (*U2FRegistration, error) {
if len(s) < 64 {
return nil, errors.New("badly encoded u2f registration")
}
b := []byte(s)
return &U2FRegistration{
PublicKey: b[:64],
KeyHandle: b[64:],
}, nil
}
// Decode returns a u2f.Registration object with the decoded public
// key ready for use in verification.
func (r *U2FRegistration) Decode() (*u2f.Registration, error) {
x, y := elliptic.Unmarshal(elliptic.P256(), r.PublicKey)
if x == nil {
return nil, errors.New("invalid public key")
}
var reg u2f.Registration
reg.PubKey.Curve = elliptic.P256()
reg.PubKey.X = x
reg.PubKey.Y = y
reg.KeyHandle = r.KeyHandle
return &reg, nil
}
// NewU2FRegistrationFromData creates a U2FRegistration from a
// u2f.Registration object.
func NewU2FRegistrationFromData(reg *u2f.Registration) *U2FRegistration {
pk := elliptic.Marshal(reg.PubKey.Curve, reg.PubKey.X, reg.PubKey.Y)
return &U2FRegistration{
PublicKey: pk,
KeyHandle: reg.KeyHandle,
}
}
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.
# 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.
// 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
}
// 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))
// 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"`
}
// 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 !=