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
- func DecryptAESGCM(key, blob []byte, tag string) ([]byte, error)
- func EncodeItem(meta ItemMetadata, itemUUID string) []byte
- func EncryptAESGCM(key, plaintext, nonce []byte, tag string) ([]byte, error)
- func NewItemKey() ([]byte, error)
- func NewItemUUID() (string, error)
- func NormalizeAPIBase(apiBase string) string
- func OpenItemContent(itemKey []byte, encContentB64 string) ([]byte, error)
- func OpenItemKey(shareKey []byte, encItemKeyB64 string) ([]byte, error)
- func OpenShareKey(sk ShareKey, encKey []byte) ([]byte, error)
- func OpenVaultContent(shareKey []byte, encContentB64 string) ([]byte, error)
- func PATKey(pat string) ([]byte, error)
- func PATToken(pat string) string
- func SealItemContent(itemKey, plaintext []byte) (string, error)
- func SealItemKey(shareKey, itemKey []byte) (string, error)
- type API
- type APIError
- type Client
- func (c *Client) Authenticate(ctx context.Context, pat string) (*Session, error)
- func (c *Client) CreateItem(ctx context.Context, s *Session, shareID string, req CreateItemRequest) (*ItemRevision, error)
- func (c *Client) DeleteItem(ctx context.Context, s *Session, shareID, itemID string, revision int) error
- func (c *Client) GetShareKeys(ctx context.Context, s *Session, shareID string) ([]ShareKey, error)
- func (c *Client) ListItems(ctx context.Context, s *Session, shareID string) ([]ItemRevision, error)
- func (c *Client) ListShares(ctx context.Context, s *Session) ([]Share, error)
- func (c *Client) UpdateItem(ctx context.Context, s *Session, shareID, itemID string, req UpdateItemRequest) (*ItemRevision, error)
- type CreateItemRequest
- type ItemMetadata
- type ItemRevision
- type Session
- type Share
- type ShareKey
- type UpdateItemRequest
Constants ¶
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).
const ( TagItemContent = "itemcontent" TagItemKey = "itemkey" TagVaultContent = "vaultcontent" )
PassEncryptionTag values are AES-GCM associated-data domain separators.
Variables ¶
This section is empty.
Functions ¶
func DecryptAESGCM ¶
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 ¶
EncryptAESGCM produces nonce || ciphertext+tag. nonce must be 12 bytes and unique per key.
func NewItemKey ¶
NewItemKey returns a fresh random 32-byte AES-256 key for one item.
func NewItemUUID ¶
NewItemUUID returns a fresh random RFC 4122 v4 UUID string, used for an item's metadata.item_uuid on create.
func NormalizeAPIBase ¶
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 ¶
OpenItemContent decrypts a base64 item Content with its item key, yielding the raw protobuf-encoded Item bytes.
func OpenItemKey ¶
OpenItemKey decrypts a base64 ItemKey (from an item revision) with the share key.
func OpenShareKey ¶
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 ¶
OpenVaultContent decrypts a base64 vault Content (Share.Content) with the share key.
func PATKey ¶
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 ¶
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 ¶
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 ¶
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)
ListItems(ctx context.Context, s *Session, shareID string) ([]ItemRevision, 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 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 (*Client) Authenticate ¶
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 ¶
GetShareKeys returns the (still-encrypted) key rotations for a share.
func (*Client) ListShares ¶
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 ¶
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 {
}
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 {
}
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).