protonpass

package
v1.12.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 14, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package protonpass is a minimal native-Go client for Proton Pass's internal HTTP API, used by the proton-pass keyring backend. It speaks the same wire protocol as the Proton Pass CLI, derived from observed traffic (clean-room): the PAT->session exchange (two POSTs) and the authenticated read endpoints (shares, items, share keys) here in client.go, the symmetric AES-GCM unwrap chain in crypto.go, and the item protobuf parse in proto.go.

Index

Constants

View Source
const (
	DefaultAPIBase     = "https://pass-api.proton.me"
	DefaultAppVersion  = "cli-pass@2.1.4"
	DefaultSDKVersions = "muon@2.5.0"
	DefaultOriginSDK   = "pass-cli@2.1.4"

	// ItemContentFormatVersion is the item-v1 content format version sent on
	// create/update. It matches the version the live API currently issues for
	// items; bump it if the server starts rejecting writes at this version.
	ItemContentFormatVersion = 6
)

Proton API defaults. The app/SDK version headers gate access; these mirror the values the Proton Pass CLI sends and can be overridden via Config. The API is unofficial, so Proton may change what it accepts and these pins can need updating over time (see the experimental note in the README).

View Source
const (
	TagItemContent  = "itemcontent"
	TagItemKey      = "itemkey"
	TagVaultContent = "vaultcontent"
	TagShareKey     = "sharekey"
)

PassEncryptionTag values are AES-GCM associated-data domain separators.

Variables

This section is empty.

Functions

func DecryptAESGCM

func DecryptAESGCM(key, blob []byte, tag string) ([]byte, error)

DecryptAESGCM opens Proton's symmetric envelope: nonce(12) || ciphertext+tag(16), AES-256-GCM, with tag as additional authenticated data.

func EncodeItem

func EncodeItem(meta ItemMetadata, itemUUID string) []byte

EncodeItem serializes a note-type item-v1 Item protobuf: metadata.name (title), metadata.note (the blob), and metadata.item_uuid, plus a Content oneof selecting the empty ItemNote variant. It is the inverse of ParseItemMetadata.

func EncryptAESGCM

func EncryptAESGCM(key, plaintext, nonce []byte, tag string) ([]byte, error)

EncryptAESGCM produces nonce || ciphertext+tag. nonce must be 12 bytes and unique per key.

func NewItemKey

func NewItemKey() ([]byte, error)

NewItemKey returns a fresh random 32-byte AES-256 key for one item.

func NewItemUUID

func NewItemUUID() (string, error)

NewItemUUID returns a fresh random RFC 4122 v4 UUID string, used for an item's metadata.item_uuid on create.

func NormalizeAPIBase

func NormalizeAPIBase(apiBase string) string

NormalizeAPIBase resolves an empty base to the default and trims a trailing slash, so callers and the session cache agree on one canonical form.

func OpenItemContent

func OpenItemContent(itemKey []byte, encContentB64 string) ([]byte, error)

OpenItemContent decrypts a base64 item Content with its item key, yielding the raw protobuf-encoded Item bytes.

func OpenItemKey

func OpenItemKey(shareKey []byte, encItemKeyB64 string) ([]byte, error)

OpenItemKey decrypts a base64 ItemKey (from an item revision) with the share key.

func OpenShareKey

func OpenShareKey(sk ShareKey, encKey []byte) ([]byte, error)

OpenShareKey decrypts a share key (an entry from GET /pass/v1/share/{id}/key) into its raw 32-byte AES key, using the PAT enc-key (from PATKey) and AAD "sharekey".

func OpenVaultContent

func OpenVaultContent(shareKey []byte, encContentB64 string) ([]byte, error)

OpenVaultContent decrypts a base64 vault Content (Share.Content) with the share key.

func PATKey

func PATKey(pat string) ([]byte, error)

PATKey decodes the "::<key>" half of a compound "pst_<token>::<key>" PAT into its raw 32-byte AES-256 key. For a PAT session this key is the symmetric root that opens the share key (see OpenShareKey); it is used directly: no salt, no KDF.

func PATToken

func PATToken(pat string) string

PATToken returns the "pst_<token>" half of a compound "pst_<token>::<key>" PAT. The "::<key>" half is the client-side vault-unwrap key and is never sent.

func SealItemContent

func SealItemContent(itemKey, plaintext []byte) (string, error)

SealItemContent encrypts an item's protobuf plaintext under its item key (AAD "itemcontent"), returning the base64 blob for ItemRevision.Content. It is the inverse of OpenItemContent.

func SealItemKey

func SealItemKey(shareKey, itemKey []byte) (string, error)

SealItemKey wraps a per-item key with the share key (AAD "itemkey"), returning the base64 blob for ItemRevision.ItemKey. It is the inverse of OpenItemKey.

Types

type API

type API interface {
	Authenticate(ctx context.Context, pat string) (*Session, error)
	ListShares(ctx context.Context, s *Session) ([]Share, error)
	ListItems(ctx context.Context, s *Session, shareID string) ([]ItemRevision, error)
	GetShareKeys(ctx context.Context, s *Session, shareID string) ([]ShareKey, error)
	CreateItem(ctx context.Context, s *Session, shareID string, req CreateItemRequest) (*ItemRevision, error)
	UpdateItem(ctx context.Context, s *Session, shareID, itemID string, req UpdateItemRequest) (*ItemRevision, error)
	DeleteItem(ctx context.Context, s *Session, shareID, itemID string, revision int) error
}

API is the subset of the Proton Pass client the keyring backend depends on (kept small so the backend can mock it in tests).

type APIError

type APIError struct {
	Status  int
	Code    int
	Message string
}

APIError is a non-2xx response or a Proton error envelope ({Code, Error}).

func (*APIError) Error

func (e *APIError) Error() string

type Client

type Client struct {
	HTTP        *http.Client
	APIBase     string
	AppVersion  string
	SDKVersions string
	OriginSDK   string
}

Client talks to the Proton Pass HTTP API over native net/http (HTTP/2, system trust roots). Proton pins server certs against its own clients, but that only affects the official clients; a standard Go client connects normally.

func New

func New(apiBase string) *Client

New returns a Client with Proton defaults; apiBase may be "" for the default.

func (*Client) Authenticate

func (c *Client) Authenticate(ctx context.Context, pat string) (*Session, error)

Authenticate performs the PAT -> session exchange: create an anonymous session, then exchange the PAT for an authenticated one.

func (*Client) CreateItem

func (c *Client) CreateItem(ctx context.Context, s *Session, shareID string, req CreateItemRequest) (*ItemRevision, error)

CreateItem creates a new item in the share and returns its new revision.

func (*Client) DeleteItem

func (c *Client) DeleteItem(ctx context.Context, s *Session, shareID, itemID string, revision int) error

DeleteItem permanently deletes an item (bypassing trash). revision is the item's current revision for the optimistic-concurrency check.

func (*Client) GetShareKeys

func (c *Client) GetShareKeys(ctx context.Context, s *Session, shareID string) ([]ShareKey, error)

GetShareKeys returns the (still-encrypted) key rotations for a share.

func (*Client) ListItems

func (c *Client) ListItems(ctx context.Context, s *Session, shareID string) ([]ItemRevision, error)

ListItems returns every item revision in a share, following pagination.

func (*Client) ListShares

func (c *Client) ListShares(ctx context.Context, s *Session) ([]Share, error)

ListShares returns the vaults/shares the session can access.

func (*Client) UpdateItem

func (c *Client) UpdateItem(ctx context.Context, s *Session, shareID, itemID string, req UpdateItemRequest) (*ItemRevision, error)

UpdateItem replaces an item's content and returns its new revision.

type CreateItemRequest

type CreateItemRequest struct {
	KeyRotation          int    `json:"KeyRotation"`
	ContentFormatVersion int    `json:"ContentFormatVersion"`
	Content              string `json:"Content"`
	ItemKey              string `json:"ItemKey"`
}

CreateItemRequest is the body of POST /pass/v1/share/{shareID}/item. Content is the base64 AES-GCM item ciphertext; ItemKey is the per-item key wrapped to the share key. KeyRotation is the share-key rotation ItemKey is wrapped under.

type ItemMetadata

type ItemMetadata struct {
	Name string
	Note string
}

ItemMetadata is the subset of a decrypted Item the backend uses: the title and the note payload.

func ParseItemMetadata

func ParseItemMetadata(item []byte) (ItemMetadata, error)

ParseItemMetadata extracts Metadata.name and Metadata.note from a decrypted Item protobuf (the plaintext returned by OpenItemContent). Unknown fields and other content types are skipped.

type ItemRevision

type ItemRevision struct {
	ItemID               string `json:"ItemID"`
	Revision             int    `json:"Revision"`
	Content              string `json:"Content"`
	ItemKey              string `json:"ItemKey"`
	ContentFormatVersion int    `json:"ContentFormatVersion"`
	KeyRotation          int    `json:"KeyRotation"`
	Flags                int    `json:"Flags"`
	CreateTime           int64  `json:"CreateTime"`
	ModifyTime           int64  `json:"ModifyTime"`
	RevisionTime         int64  `json:"RevisionTime"`
}

ItemRevision is one entry from GET /pass/v1/share/{shareID}/item. Content is the base64 AES-256-GCM ciphertext (decrypts to a protobuf); ItemKey is the per-item key wrapped to the share key.

type Session

type Session struct {
	UID          string
	AccessToken  string
	RefreshToken string

	// AccessExpiry is the access token's expiry as reported by the exchange.
	// Proton sends it as AccessExpirationTime; it is treated as Unix seconds
	// when it looks like a plausible future epoch and ignored otherwise (0 if
	// absent). The session-cache freshness check, not this client, decides how
	// to interpret it.
	AccessExpiry int64
}

Session is an authenticated Proton session: a UID plus bearer tokens.

type Share

type Share struct {
	ShareID              string `json:"ShareID"`
	VaultID              string `json:"VaultID"`
	TargetID             string `json:"TargetID"`
	TargetType           int    `json:"TargetType"`
	AddressID            string `json:"AddressID"`
	Content              string `json:"Content"`
	ContentFormatVersion int    `json:"ContentFormatVersion"`
	ContentKeyRotation   int    `json:"ContentKeyRotation"`
	Permission           int    `json:"Permission"`
	ShareRoleID          string `json:"ShareRoleID"`
	Owner                bool   `json:"Owner"`
	Primary              bool   `json:"Primary"`
	Shared               bool   `json:"Shared"`
	CreateTime           int64  `json:"CreateTime"`
}

Share is one entry from GET /pass/v1/share (a vault the session can access). Content is the base64 vault metadata, AES-256-GCM encrypted under the share key (AAD "vaultcontent"); decryption is the backend's job (see OpenVaultContent).

type ShareKey

type ShareKey struct {
	KeyRotation int    `json:"KeyRotation"`
	Key         string `json:"Key"`
	UserKeyID   string `json:"UserKeyID"`
	CreateTime  int64  `json:"CreateTime"`
}

ShareKey is one entry from GET /pass/v1/share/{shareID}/key. For a PAT session Key is the base64 share key, AES-256-GCM enveloped with the PAT's "::<key>" (AAD "sharekey"); UserKeyID is unused on the PAT path. Decryption is the backend's job (see crypto.go OpenShareKey).

type UpdateItemRequest

type UpdateItemRequest struct {
	KeyRotation          int    `json:"KeyRotation"`
	LastRevision         int    `json:"LastRevision"`
	Content              string `json:"Content"`
	ContentFormatVersion int    `json:"ContentFormatVersion"`
}

UpdateItemRequest is the body of PUT /pass/v1/share/{shareID}/item/{itemID}. The per-item key is unchanged on update, so only the re-encrypted Content is sent; LastRevision is the revision being replaced (optimistic concurrency).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL