Ejemplo n.º 1
0
func (sec *AccountSecurity) ResetPassword(kms security.KMS, password string) (*AccountSecurity, error) {
	kek := sec.SystemKey.Clone()
	if err := kms.DecryptKey(&kek); err != nil {
		return nil, fmt.Errorf("key decryption error: %s", err)
	}
	kek.IV = make([]byte, ClientKeyType.BlockSize())
	copy(kek.IV, sec.Nonce)

	clientKey := security.KeyFromPasscode([]byte(password), sec.Nonce, sec.UserKey.KeyType)
	if err := kek.Encrypt(clientKey); err != nil {
		return nil, fmt.Errorf("key encryption error: %s", err)
	}

	var (
		mac [16]byte
		key [32]byte
	)
	copy(key[:], clientKey.Plaintext)
	poly1305.Sum(&mac, sec.Nonce, &key)

	nsec := &AccountSecurity{
		Nonce:     sec.Nonce,
		MAC:       mac[:],
		SystemKey: sec.SystemKey,
		UserKey:   kek,
		KeyPair:   sec.KeyPair,
	}
	return nsec, nil
}
Ejemplo n.º 2
0
Archivo: pm.go Proyecto: logan/heim
func (pm *PM) transmitToAgent(kms security.KMS, client *Client) (*security.ManagedKey, error) {
	if client.UserID() != pm.Receiver {
		return nil, ErrAccessDenied
	}

	// Decrypt PM key
	pmKey := pm.EncryptedSystemKey.Clone()
	if err := kms.DecryptKey(&pmKey); err != nil {
		return nil, err
	}

	// Verify ReceiverMAC
	if err := pm.verifyKey(&pmKey); err != nil {
		return nil, err
	}

	// Encrypt PM key for agent
	encryptedPMKey := pmKey.Clone()
	encryptedPMKey.IV = pm.IV
	if err := encryptedPMKey.Encrypt(client.Authorization.ClientKey); err != nil {
		return nil, err
	}

	pm.EncryptedReceiverKey = &encryptedPMKey
	return &pmKey, nil
}
Ejemplo n.º 3
0
Archivo: room.go Proyecto: logan/heim
func (r *memRoom) GenerateMessageKey(ctx scope.Context, kms security.KMS) (proto.RoomMessageKey, error) {
	nonce, err := kms.GenerateNonce(security.AES128.KeySize())
	if err != nil {
		return nil, err
	}

	mkey, err := kms.GenerateEncryptedKey(security.AES128, "room", r.name)
	if err != nil {
		return nil, err
	}

	kp := r.managerKey.KeyPair()
	r.messageKey = &roomMessageKey{
		GrantManager: &proto.GrantManager{
			Capabilities:     &capabilities{},
			Managers:         r.managerKey,
			KeyEncryptingKey: &r.sec.KeyEncryptingKey,
			SubjectKeyPair:   &kp,
			SubjectNonce:     nonce,
		},
		timestamp: time.Now(),
		nonce:     nonce,
		key:       *mkey,
	}
	r.messageKey.id = fmt.Sprintf("%s", r.messageKey.timestamp)
	return r.messageKey, nil
}
Ejemplo n.º 4
0
func (b *AccountManagerBinding) getOTP(db gorp.SqlExecutor, kms security.KMS, accountID snowflake.Snowflake) (*proto.OTP, error) {
	encryptedOTP, err := b.getRawOTP(db, accountID)
	if err != nil {
		return nil, err
	}

	key := security.ManagedKey{
		KeyType:      OTPKeyType,
		IV:           encryptedOTP.IV,
		Ciphertext:   encryptedOTP.EncryptedKey,
		ContextKey:   "account",
		ContextValue: accountID.String(),
	}
	if err := kms.DecryptKey(&key); err != nil {
		return nil, err
	}

	uriBytes, err := security.DecryptGCM(&key, encryptedOTP.IV, encryptedOTP.Digest, encryptedOTP.EncryptedURI, nil)
	if err != nil {
		return nil, err
	}

	otp := &proto.OTP{
		URI:       string(uriBytes),
		Validated: encryptedOTP.Validated,
	}
	return otp, nil
}
Ejemplo n.º 5
0
Archivo: grants.go Proyecto: logan/heim
func (gs *GrantManager) StaffGrantToAccount(ctx scope.Context, kms security.KMS, target Account) error {
	keyEncryptingKey := gs.KeyEncryptingKey.Clone()
	if err := kms.DecryptKey(&keyEncryptingKey); err != nil {
		return fmt.Errorf("key-encrypting-key decrypt error: %s", err)
	}

	subjectKeyPair := gs.SubjectKeyPair.Clone()
	if err := subjectKeyPair.Decrypt(&keyEncryptingKey); err != nil {
		return err
	}

	// TODO: customize public/private payloads
	kp := target.KeyPair()
	var payloadKey security.ManagedKey
	if gs.PayloadKey == nil {
		payloadKey = keyEncryptingKey
	} else {
		payloadKey = gs.PayloadKey.Clone()
		if err := kms.DecryptKey(&payloadKey); err != nil {
			return fmt.Errorf("payload-key decrypt error: %s", err)
		}
	}
	c, err := security.GrantPublicKeyCapability(
		kms, gs.SubjectNonce, &subjectKeyPair, &kp, nil, payloadKey.Plaintext)
	if err != nil {
		return err
	}

	return gs.Capabilities.Save(ctx, target, c)
}
Ejemplo n.º 6
0
Archivo: room.go Proyecto: logan/heim
func (r *roomManagerKey) StaffUnlock(kms security.KMS) (*security.ManagedKeyPair, error) {
	kek := r.RoomSecurity.KeyEncryptingKey.Clone()
	if err := kms.DecryptKey(&kek); err != nil {
		return nil, err
	}

	kp := r.KeyPair()
	if err := kp.Decrypt(&kek); err != nil {
		return nil, err
	}
	return &kp, nil
}
Ejemplo n.º 7
0
Archivo: pm.go Proyecto: logan/heim
func (pm *PM) transmitToAccount(kms security.KMS, pmKey *security.ManagedKey, receiver Account) (*PM, error) {
	userKey := receiver.SystemKey()
	if err := kms.DecryptKey(&userKey); err != nil {
		return nil, err
	}

	encryptedReceiverKey := pmKey.Clone()
	encryptedReceiverKey.IV = pm.IV
	if err := encryptedReceiverKey.Encrypt(&userKey); err != nil {
		return nil, err
	}

	pm.EncryptedReceiverKey = &encryptedReceiverKey
	return pm, nil
}
Ejemplo n.º 8
0
Archivo: pm.go Proyecto: logan/heim
func (pm *PM) upgradeToAccountReceiver(ctx scope.Context, b Backend, kms security.KMS, client *Client) (*security.ManagedKey, error) {
	// Verify that client and receiver agent share the same account.
	_, id := pm.Receiver.Parse()
	agent, err := b.AgentTracker().Get(ctx, id)
	if err != nil {
		return nil, err
	}
	if agent.AccountID != client.Account.ID().String() {
		return nil, ErrAccessDenied
	}

	// Unlock PM and verify Receiver.
	pmKey := pm.EncryptedSystemKey.Clone()
	if err := kms.DecryptKey(&pmKey); err != nil {
		return nil, err
	}
	if err := pm.verifyKey(&pmKey); err != nil {
		return nil, err
	}

	// Re-encrypt PM key for account.
	pm.Receiver = UserID(fmt.Sprintf("account:%s", client.Account.ID()))
	var (
		mac [16]byte
		key [32]byte
	)
	copy(key[:], pmKey.Plaintext)
	poly1305.Sum(&mac, []byte(pm.Receiver), &key)
	pm.ReceiverMAC = mac[:]

	userKey := client.Account.UserKey()
	if err := userKey.Decrypt(client.Authorization.ClientKey); err != nil {
		return nil, err
	}

	encryptedReceiverKey := pmKey.Clone()
	encryptedReceiverKey.IV = pm.IV
	if err := encryptedReceiverKey.Encrypt(&userKey); err != nil {
		return nil, err
	}
	pm.EncryptedReceiverKey = &encryptedReceiverKey

	if err := pm.verifyKey(&pmKey); err != nil {
		return nil, err
	}

	return &pmKey, nil
}
Ejemplo n.º 9
0
func (rmkb *RoomManagerKeyBinding) StaffUnlock(kms security.KMS) (*security.ManagedKeyPair, error) {
	kek := security.ManagedKey{
		KeyType:      proto.RoomManagerKeyType,
		Ciphertext:   rmkb.Room.EncryptedManagementKey,
		ContextKey:   "room",
		ContextValue: rmkb.Room.Name,
	}
	if err := kms.DecryptKey(&kek); err != nil {
		return nil, err
	}
	kp := rmkb.KeyPair()
	if err := kp.Decrypt(&kek); err != nil {
		return nil, err
	}
	return &kp, nil
}
Ejemplo n.º 10
0
Archivo: heim.go Proyecto: rmasoni/heim
func CheckEmailVerificationToken(kms security.KMS, account Account, email string, token []byte) error {
	systemKey := account.SystemKey()
	if err := kms.DecryptKey(&systemKey); err != nil {
		return err
	}

	expected, err := emailVerificationToken(&systemKey, email)
	if err != nil {
		return err
	}

	if !hmac.Equal(token, expected) {
		return ErrInvalidVerificationToken
	}

	return nil
}
Ejemplo n.º 11
0
Archivo: etcd.go Proyecto: robot0x/heim
func (e *etcdCluster) setSecret(kms security.KMS, name string, bytes int) ([]byte, error) {
	// Generate our own key.
	secret, err := kms.GenerateNonce(bytes)
	if err != nil {
		return nil, err
	}

	// Try to stake our claim on this secret.
	if _, err := e.c.Create(e.key("/secrets/%s", name), hex.EncodeToString(secret), 0); err != nil {
		if etcdErr, ok := err.(*etcd.EtcdError); ok && etcdErr.ErrorCode == 105 {
			// Lost the race, try to use GetSecret again.
			return e.GetSecret(kms, name, bytes)
		}
		return nil, err
	}

	return secret, nil
}
Ejemplo n.º 12
0
Archivo: room.go Proyecto: logan/heim
func (r *Room) generateMessageKey(b *Backend, kms security.KMS) (*RoomMessageKeyBinding, error) {
	// Generate unique ID for storing new key in DB.
	keyID, err := snowflake.New()
	if err != nil {
		return nil, err
	}

	// Use KMS to generate nonce and key.
	nonce, err := kms.GenerateNonce(proto.RoomManagerKeyType.KeySize())
	if err != nil {
		return nil, err
	}

	mkey, err := kms.GenerateEncryptedKey(proto.RoomManagerKeyType, "room", r.Name)
	if err != nil {
		return nil, err
	}

	return NewRoomMessageKeyBinding(r.Bind(b), keyID, mkey, nonce), nil
}
Ejemplo n.º 13
0
Archivo: pm.go Proyecto: logan/heim
func NewPM(kms security.KMS, client *Client, initiatorNick string, receiver UserID, receiverNick string) (
	*PM, *security.ManagedKey, error) {

	if client.Account == nil {
		return nil, nil, ErrAccessDenied
	}

	pmID, err := snowflake.New()
	if err != nil {
		return nil, nil, err
	}

	iv, err := kms.GenerateNonce(RoomMessageKeyType.BlockSize())
	if err != nil {
		return nil, nil, err
	}

	encryptedSystemKey, err := kms.GenerateEncryptedKey(RoomMessageKeyType, "pm", pmID.String())
	if err != nil {
		return nil, nil, err
	}

	pmKey := encryptedSystemKey.Clone()
	if err := kms.DecryptKey(&pmKey); err != nil {
		return nil, nil, fmt.Errorf("pm key decrypt: %s", err)
	}
	//pmKey.IV = iv

	userKey := client.Account.UserKey()
	if err := userKey.Decrypt(client.Authorization.ClientKey); err != nil {
		return nil, nil, fmt.Errorf("initiator account key decrypt: %s", err)
	}

	encryptedInitiatorKey := pmKey.Clone()
	encryptedInitiatorKey.IV = iv
	if err := encryptedInitiatorKey.Encrypt(&userKey); err != nil {
		return nil, nil, fmt.Errorf("initiator pm key encrypt: %s", err)
	}

	var (
		mac [16]byte
		key [32]byte
	)
	copy(key[:], pmKey.Plaintext)
	poly1305.Sum(&mac, []byte(receiver), &key)

	pm := &PM{
		ID:                    pmID,
		Initiator:             client.Account.ID(),
		InitiatorNick:         initiatorNick,
		Receiver:              receiver,
		ReceiverNick:          receiverNick,
		ReceiverMAC:           mac[:],
		IV:                    iv,
		EncryptedSystemKey:    encryptedSystemKey,
		EncryptedInitiatorKey: &encryptedInitiatorKey,
	}
	return pm, &pmKey, nil
}
Ejemplo n.º 14
0
func (tc *TestCluster) GetSecret(kms security.KMS, name string, bytes int) ([]byte, error) {
	tc.Lock()
	defer tc.Unlock()

	if secret, ok := tc.secrets[name]; ok {
		if len(secret) != bytes {
			return nil, fmt.Errorf("secret inconsistent: expected %d bytes, got %d", bytes, len(secret))
		}
		return secret, nil
	}

	secret, err := kms.GenerateNonce(bytes)
	if err != nil {
		return nil, err
	}

	if tc.secrets == nil {
		tc.secrets = map[string][]byte{name: secret}
	} else {
		tc.secrets[name] = secret
	}
	return secret, nil
}
Ejemplo n.º 15
0
func GeneratePasswordResetRequest(
	kms security.KMS, accountID snowflake.Snowflake) (*PasswordResetRequest, error) {

	id, err := snowflake.New()
	if err != nil {
		return nil, err
	}

	key, err := kms.GenerateNonce(sha256.BlockSize)
	if err != nil {
		return nil, err
	}

	now := time.Now()
	req := &PasswordResetRequest{
		ID:        id,
		AccountID: accountID,
		Key:       key,
		Requested: now,
		Expires:   now.Add(PasswordResetRequestLifetime),
	}
	return req, nil
}
Ejemplo n.º 16
0
Archivo: room.go Proyecto: robot0x/heim
func NewRoomSecurity(kms security.KMS, roomName string) (*RoomSecurity, error) {
	kpType := security.Curve25519

	// Use one KMS request to obtain all the randomness we need:
	//   - key-encrypting-key IV
	//   - private key for grants to accounts
	//   - nonce for manager grants to accounts
	randomData, err := kms.GenerateNonce(
		RoomManagerKeyType.BlockSize() + kpType.PrivateKeySize() + kpType.NonceSize())
	if err != nil {
		return nil, fmt.Errorf("rng error: %s", err)
	}
	randomReader := bytes.NewReader(randomData)

	// Generate IV with random data.
	iv := make([]byte, RoomManagerKeyType.BlockSize())
	if _, err := io.ReadFull(randomReader, iv); err != nil {
		return nil, fmt.Errorf("rng error: %s", err)
	}

	// Generate private key using randomReader.
	keyPair, err := kpType.Generate(randomReader)
	if err != nil {
		return nil, fmt.Errorf("keypair generation error: %s", err)
	}

	// Generate nonce with random data.
	nonce := make([]byte, kpType.NonceSize())
	if _, err := io.ReadFull(randomReader, nonce); err != nil {
		return nil, fmt.Errorf("rng error: %s", err)
	}

	// Generate key-encrypting-key. This will be returned encrypted, using the
	// name of the room as its context.
	encryptedKek, err := kms.GenerateEncryptedKey(RoomManagerKeyType, "room", roomName)
	if err != nil {
		return nil, fmt.Errorf("key generation error: %s", err)
	}

	// Decrypt key-encrypting-key so we can encrypt keypair.
	kek := encryptedKek.Clone()
	if err = kms.DecryptKey(&kek); err != nil {
		return nil, fmt.Errorf("key decryption error: %s", err)
	}

	// Encrypt private key.
	keyPair.IV = iv
	if err = keyPair.Encrypt(&kek); err != nil {
		return nil, fmt.Errorf("keypair encryption error: %s", err)
	}

	// Generate message authentication code, for verifying a given key-encryption-key.
	var (
		mac [16]byte
		key [32]byte
	)
	copy(key[:], kek.Plaintext)
	poly1305.Sum(&mac, iv, &key)

	sec := &RoomSecurity{
		Nonce:            nonce,
		MAC:              mac[:],
		KeyEncryptingKey: *encryptedKek,
		KeyPair:          *keyPair,
	}
	return sec, nil
}
Ejemplo n.º 17
0
func (b *AccountManagerBinding) GenerateOTP(ctx scope.Context, heim *proto.Heim, kms security.KMS, account proto.Account) (*proto.OTP, error) {
	encryptedKey, err := kms.GenerateEncryptedKey(OTPKeyType, "account", account.ID().String())
	if err != nil {
		return nil, err
	}

	key := encryptedKey.Clone()
	if err := kms.DecryptKey(&key); err != nil {
		return nil, err
	}

	iv, err := kms.GenerateNonce(OTPKeyType.BlockSize())
	if err != nil {
		return nil, err
	}

	t, err := b.DbMap.Begin()
	if err != nil {
		return nil, err
	}

	rawOTP, err := b.getRawOTP(t, account.ID())
	if err != nil && err != proto.ErrOTPNotEnrolled {
		rollback(ctx, t)
		return nil, err
	}
	if err == nil {
		if rawOTP.Validated {
			rollback(ctx, t)
			return nil, proto.ErrOTPAlreadyEnrolled
		}
		row := &OTP{AccountID: account.ID().String()}
		if _, err := t.Delete(row); err != nil {
			rollback(ctx, t)
			return nil, err
		}
	}

	otp, err := heim.NewOTP(account)
	if err != nil {
		rollback(ctx, t)
		return nil, err
	}

	digest, encryptedURI, err := security.EncryptGCM(&key, iv, []byte(otp.URI), nil)
	if err != nil {
		rollback(ctx, t)
		return nil, err
	}

	row := &OTP{
		AccountID:    account.ID().String(),
		IV:           iv,
		EncryptedKey: encryptedKey.Ciphertext,
		Digest:       digest,
		EncryptedURI: encryptedURI,
	}
	if err := t.Insert(row); err != nil {
		// TODO: this could fail in the case of a race condition
		// by the time that matters we should be on postgres 9.5 and using a proper upsert
		rollback(ctx, t)
		return nil, err
	}

	if err := t.Commit(); err != nil {
		return nil, err
	}

	return otp, nil
}
Ejemplo n.º 18
0
Archivo: room.go Proyecto: logan/heim
func NewRoom(
	ctx scope.Context, kms security.KMS, private bool, name, version string, managers ...proto.Account) (
	proto.ManagedRoom, error) {

	sec, err := proto.NewRoomSecurity(kms, name)
	if err != nil {
		return nil, err
	}

	managerKey := sec.KeyEncryptingKey.Clone()
	if err := kms.DecryptKey(&managerKey); err != nil {
		return nil, err
	}
	roomKeyPair, err := sec.Unlock(&managerKey)
	if err != nil {
		return nil, err
	}

	room := &memRoom{
		RoomBase: RoomBase{
			name:      name,
			version:   version,
			log:       newMemLog(),
			agentBans: map[proto.UserID]time.Time{},
			ipBans:    map[string]time.Time{},
		},
		sec: sec,
		managerKey: &roomManagerKey{
			RoomSecurity: sec,
			GrantManager: &proto.GrantManager{
				Capabilities:     &capabilities{},
				KeyEncryptingKey: &sec.KeyEncryptingKey,
				SubjectKeyPair:   &sec.KeyPair,
				SubjectNonce:     sec.Nonce,
			},
		},
	}
	room.managerKey.GrantManager.Managers = room.managerKey

	var (
		roomMsgKey proto.RoomMessageKey
		msgKey     security.ManagedKey
	)
	if private {
		roomMsgKey, err = room.GenerateMessageKey(ctx, kms)
		if err != nil {
			return nil, err
		}

		msgKey = roomMsgKey.ManagedKey()
		if err := kms.DecryptKey(&msgKey); err != nil {
			return nil, err
		}
	}

	for _, manager := range managers {
		kp := manager.KeyPair()
		c, err := security.GrantPublicKeyCapability(
			kms, sec.Nonce, roomKeyPair, &kp, nil, managerKey.Plaintext)
		if err != nil {
			return nil, err
		}
		room.managerKey.Capabilities.Save(ctx, manager, c)

		if private {
			c, err = security.GrantPublicKeyCapability(
				kms, roomMsgKey.Nonce(), roomKeyPair, &kp, nil, msgKey.Plaintext)
			if err != nil {
				return nil, err
			}
			room.messageKey.Capabilities.Save(ctx, manager, c)
		}
	}

	return room, nil
}
Ejemplo n.º 19
0
func (b *Backend) CreateRoom(
	ctx scope.Context, kms security.KMS, private bool, name string, managers ...proto.Account) (
	proto.ManagedRoom, error) {

	sec, err := proto.NewRoomSecurity(kms, name)
	if err != nil {
		return nil, err
	}

	logging.Logger(ctx).Printf("creating room: %s", name)
	room := &Room{
		Name:  name,
		IV:    sec.KeyPair.IV,
		MAC:   sec.MAC,
		Nonce: sec.Nonce,
		EncryptedManagementKey: sec.KeyEncryptingKey.Ciphertext,
		EncryptedPrivateKey:    sec.KeyPair.EncryptedPrivateKey,
		PublicKey:              sec.KeyPair.PublicKey,
	}

	var (
		rmkb   *RoomMessageKeyBinding
		msgKey security.ManagedKey
	)
	if private {
		rmkb, err = room.generateMessageKey(b, kms)
		if err != nil {
			return nil, err
		}

		msgKey = rmkb.ManagedKey()
		if err := kms.DecryptKey(&msgKey); err != nil {
			return nil, err
		}
	}

	// Generate manager capabilities.
	managerKey := sec.KeyEncryptingKey.Clone()
	if err := kms.DecryptKey(&managerKey); err != nil {
		return nil, fmt.Errorf("manager key decrypt error: %s", err)
	}
	roomKeyPair, err := sec.Unlock(&managerKey)
	if err != nil {
		return nil, fmt.Errorf("room security unlock error: %s", err)
	}
	managerCaps := make([]*security.PublicKeyCapability, len(managers))
	for i, manager := range managers {
		kp := manager.KeyPair()
		c, err := security.GrantPublicKeyCapability(
			kms, sec.Nonce, roomKeyPair, &kp, nil, managerKey.Plaintext)
		if err != nil {
			return nil, fmt.Errorf("manager grant error: %s", err)
		}
		managerCaps[i] = c
	}

	accessCaps := []*security.PublicKeyCapability{}
	if private {
		accessCaps = make([]*security.PublicKeyCapability, len(managers))
		for i, manager := range managers {
			kp := manager.KeyPair()
			c, err := security.GrantPublicKeyCapability(
				kms, rmkb.Nonce(), roomKeyPair, &kp, nil, msgKey.Plaintext)
			if err != nil {
				return nil, fmt.Errorf("access grant error: %s", err)
			}
			accessCaps[i] = c
		}
	}

	// Insert data.
	t, err := b.DbMap.Begin()
	if err != nil {
		return nil, err
	}

	rollback := func() {
		if err := t.Rollback(); err != nil {
			logging.Logger(ctx).Printf("rollback error: %s", err)
		}
	}

	if err := t.Insert(room); err != nil {
		logging.Logger(ctx).Printf("room creation error on %s: %s", name, err)
		rollback()
		return nil, err
	}

	if rmkb != nil {
		if err := t.Insert(&rmkb.MessageKey, &rmkb.RoomMessageKey); err != nil {
			logging.Logger(ctx).Printf("room creation error on %s (message key): %s", name, err)
			rollback()
			return nil, err
		}
	}

	managerCapTable := RoomManagerCapabilities{
		Room:     room,
		Executor: t,
	}
	for i, capability := range managerCaps {
		if err := managerCapTable.Save(ctx, managers[i], capability); err != nil {
			logging.Logger(ctx).Printf(
				"room creation error on %s (manager %s): %s", name, managers[i].ID().String(), err)
			rollback()
			return nil, err
		}
	}

	messageCapTable := RoomMessageCapabilities{
		Room:     room,
		Executor: t,
	}
	for i, capability := range accessCaps {
		if err := messageCapTable.Save(ctx, managers[i], capability); err != nil {
			logging.Logger(ctx).Printf(
				"room creation error on %s (access capability): %s", name, err)
			rollback()
			return nil, err
		}
	}

	if err := t.Commit(); err != nil {
		logging.Logger(ctx).Printf("room creation error on %s (commit): %s", name, err)
		return nil, err
	}

	return room.Bind(b), nil
}
Ejemplo n.º 20
0
// NewAccountSecurity initializes the nonce and account secrets for a new account
// with the given password. Returns an encrypted key-encrypting-key, encrypted
// key-pair, nonce, and error.
func NewAccountSecurity(
	kms security.KMS, password string) (*AccountSecurity, *security.ManagedKey, error) {

	kpType := security.Curve25519

	// Use one KMS request to obtain all the randomness we need:
	//   - nonce
	//   - private key
	randomData, err := kms.GenerateNonce(kpType.NonceSize() + kpType.PrivateKeySize())
	if err != nil {
		return nil, nil, fmt.Errorf("rng error: %s", err)
	}
	randomReader := bytes.NewReader(randomData)

	// Generate nonce with random data. Use to populate IV.
	nonce := make([]byte, kpType.NonceSize())
	if _, err := io.ReadFull(randomReader, nonce); err != nil {
		return nil, nil, fmt.Errorf("rng error: %s", err)
	}
	iv := make([]byte, ClientKeyType.BlockSize())
	copy(iv, nonce)

	// Generate key-encrypting-key using KMS. This will be returned encrypted,
	// using the base64 encoding of the nonce as its context.
	nonceBase64 := base64.URLEncoding.EncodeToString(nonce)
	systemKey, err := kms.GenerateEncryptedKey(ClientKeyType, "nonce", nonceBase64)
	if err != nil {
		return nil, nil, fmt.Errorf("key generation error: %s", err)
	}

	// Generate private key using randomReader.
	keyPair, err := kpType.Generate(randomReader)
	if err != nil {
		return nil, nil, fmt.Errorf("keypair generation error: %s", err)
	}

	// Decrypt key-encrypting-key so we can encrypt keypair, and so we can re-encrypt
	// it using the user's key.
	kek := systemKey.Clone()
	if err = kms.DecryptKey(&kek); err != nil {
		return nil, nil, fmt.Errorf("key decryption error: %s", err)
	}

	// Encrypt private key.
	keyPair.IV = iv
	if err = keyPair.Encrypt(&kek); err != nil {
		return nil, nil, fmt.Errorf("keypair encryption error: %s", err)
	}

	// Clone key-encrypting-key and encrypt with client key.
	clientKey := security.KeyFromPasscode([]byte(password), nonce, ClientKeyType)
	userKey := kek.Clone()
	userKey.IV = iv
	if err := userKey.Encrypt(clientKey); err != nil {
		return nil, nil, fmt.Errorf("key encryption error: %s", err)
	}

	// Generate message authentication code, for verifying passwords.
	var (
		mac [16]byte
		key [32]byte
	)
	copy(key[:], clientKey.Plaintext)
	poly1305.Sum(&mac, nonce, &key)

	sec := &AccountSecurity{
		Nonce:     nonce,
		MAC:       mac[:],
		SystemKey: *systemKey,
		UserKey:   userKey,
		KeyPair:   *keyPair,
	}
	return sec, clientKey, nil
}