Skip to content
Snippets Groups Projects
Commit 301958e3 authored by ale's avatar ale
Browse files

Add library for common composite types used in LDAP backend

This is shared code used by id/auth, id/keystore, and others. Unifying
it into its own package helps prevent drift among implementations.
parent 2934fd63
Branches
No related tags found
No related merge requests found
......@@ -9,9 +9,13 @@ A quick overview of the contents:
"RPC" implementation, just JSON POST requests but with retries,
backoff, timeouts, tracing, etc.
* [server implementation of a line-based protocol over a UNIX socket](unix/)
* [server implementation of a generic line-based protocol over a UNIX
socket](unix/).
* a [LDAP connection pool](ldap/)
* a [LDAP connection pool](ldap/).
* utilities to [serialize composite data types](ldap/compositetypes/)
used in our LDAP database.
* a [password hashing library](pwhash/) that uses fancy advanced
crypto by default but is also backwards compatible with old
......@@ -19,4 +23,3 @@ A quick overview of the contents:
* utilities to [manage encryption keys](userenckey/), themselves
encrypted with a password and a KDF.
// 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,
}
}
package compositetypes
import (
"testing"
"github.com/google/go-cmp/cmp"
)
func TestAppSpecificPassword_Serialization(t *testing.T) {
asp := &AppSpecificPassword{
ID: "abc",
Service: "service",
EncryptedPassword: "$1$1234$5678abcdef",
Comment: "this: is a comment with a colon in it",
}
out, err := UnmarshalAppSpecificPassword(asp.Marshal())
if err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if diffs := cmp.Diff(asp, out); diffs != "" {
t.Fatalf("result differs: %s", diffs)
}
}
func TestEncryptedKey_Serialization(t *testing.T) {
key := &EncryptedKey{
ID: "main",
EncryptedKey: []byte("this is a very secret key\x00"),
}
out, err := UnmarshalEncryptedKey(key.Marshal())
if err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if diffs := cmp.Diff(key, out); diffs != "" {
t.Fatalf("result differs: %s", diffs)
}
}
func TestU2FRegistration_Serialization(t *testing.T) {
key := &U2FRegistration{
KeyHandle: []byte("\x04\xc8\x1d\xd3\x9e~/\xb8\xedG(\xcb\x82\xf1\x0f\xb5\xac\xd6\xaf~\xc7\xfa\xb8\x96P\x91\xecJ\xa1,TRF\x88\xd1\x1a\xdaQ<\xd8-a\xd7\xb0\xd9v\xd7\xe8f\x8e\xab\xf6\x10\x895\xe6\x9f\xf3\x86;\xab\xc1\xae\x83^"),
PublicKey: []byte("w'Ra\xd8\x17\xdf\x86\x06\xb0\xd0\x8f\x0eI\x98\xd7\xc1\xf7\xb0}j\xc3\x1c8\xf0\x8fh\xcf\xe0\x84W\xc6\xa3\x1d\xc8e/\xa5]v \xfa]\xa5\xfb\xd5c\xbe\xc72\xb9\x80\xa9\xc0O\xd1\xe5\x9d\xe0\xcd\x19q@\xeb"),
}
out, err := UnmarshalU2FRegistration(key.Marshal())
if err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if diffs := cmp.Diff(key, out); diffs != "" {
t.Fatalf("result differs: %s", diffs)
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment