diff --git a/backend/ldap.go b/backend/ldap.go index eee6090c6c4fd8fb87dd2051bd6a4aa82f668ed9..8832df79e02b0c7ad777b59f19b74fd359a03759 100644 --- a/backend/ldap.go +++ b/backend/ldap.go @@ -1,7 +1,6 @@ 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 diff --git a/vendor/git.autistici.org/ai3/go-common/ldap/compositetypes/composite_types.go b/vendor/git.autistici.org/ai3/go-common/ldap/compositetypes/composite_types.go new file mode 100644 index 0000000000000000000000000000000000000000..32a1978c066daad0bf8edc452e61f73f8ac5d818 --- /dev/null +++ b/vendor/git.autistici.org/ai3/go-common/ldap/compositetypes/composite_types.go @@ -0,0 +1,158 @@ +// 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 ®, 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, + } +} diff --git a/vendor/github.com/tstranex/u2f/LICENSE b/vendor/github.com/tstranex/u2f/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..3c7279c6fc954181e6686bbd495e31cec53365d0 --- /dev/null +++ b/vendor/github.com/tstranex/u2f/LICENSE @@ -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. diff --git a/vendor/github.com/tstranex/u2f/README.md b/vendor/github.com/tstranex/u2f/README.md new file mode 100644 index 0000000000000000000000000000000000000000..95de78f8b5fc415857337860ac7dcc874e03e72e --- /dev/null +++ b/vendor/github.com/tstranex/u2f/README.md @@ -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. diff --git a/vendor/github.com/tstranex/u2f/auth.go b/vendor/github.com/tstranex/u2f/auth.go new file mode 100644 index 0000000000000000000000000000000000000000..05c25f573101d96c10337932bd28dd87e3f02503 --- /dev/null +++ b/vendor/github.com/tstranex/u2f/auth.go @@ -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 +} diff --git a/vendor/github.com/tstranex/u2f/certs.go b/vendor/github.com/tstranex/u2f/certs.go new file mode 100644 index 0000000000000000000000000000000000000000..14d745a0095b565c9b8bdf00b892281f87105963 --- /dev/null +++ b/vendor/github.com/tstranex/u2f/certs.go @@ -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)) diff --git a/vendor/github.com/tstranex/u2f/messages.go b/vendor/github.com/tstranex/u2f/messages.go new file mode 100644 index 0000000000000000000000000000000000000000..a78038dea297e1edd9d7ecbea657d22042d67422 --- /dev/null +++ b/vendor/github.com/tstranex/u2f/messages.go @@ -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"` +} diff --git a/vendor/github.com/tstranex/u2f/register.go b/vendor/github.com/tstranex/u2f/register.go new file mode 100644 index 0000000000000000000000000000000000000000..da0c1cce246c2fb32547a31e6f3c3e203aa66bbb --- /dev/null +++ b/vendor/github.com/tstranex/u2f/register.go @@ -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 +} diff --git a/vendor/github.com/tstranex/u2f/util.go b/vendor/github.com/tstranex/u2f/util.go new file mode 100644 index 0000000000000000000000000000000000000000..f035aa417bffe8d55229e57e33501e0a628c18e1 --- /dev/null +++ b/vendor/github.com/tstranex/u2f/util.go @@ -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 +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 85e292fa96f1b50ea1174f278b2d323456c0f394..d2f3465b6984910600e36024e04d70d104cd7d17 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -20,6 +20,12 @@ "revision": "2934fd63c275d37b0fe60afabb484a251662bd49", "revisionTime": "2019-02-17T09:01:06Z" }, + { + "checksumSHA1": "X14iCbFCOfaIai/TPi4VJ/OBZjc=", + "path": "git.autistici.org/ai3/go-common/ldap/compositetypes", + "revision": "301958e3493e263eb6ea269bf7b8644fbcd97394", + "revisionTime": "2019-03-21T10:42:03Z" + }, { "checksumSHA1": "TKGUNmKxj7KH3qhwiCh/6quUnwc=", "path": "git.autistici.org/ai3/go-common/serverutil", @@ -236,6 +242,12 @@ "revision": "971941c0819da74ed7c5d5329b7cebaa5a6f276c", "revisionTime": "2018-10-23T08:20:22Z" }, + { + "checksumSHA1": "NE1kNfAZ0AAXCUbwx196os/DSUE=", + "path": "github.com/tstranex/u2f", + "revision": "d21a03e0b1d9fc1df59ff54e7a513655c1748b0c", + "revisionTime": "2018-05-05T18:51:14Z" + }, { "checksumSHA1": "C9EIZQEMR5q5zVZCo1OtPWSV39I=", "path": "go.opencensus.io",