rypt

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 16, 2026 License: MIT Imports: 20 Imported by: 0

README

rypt

Go Reference CI

The Go client for rypt, encryption as a service in front of Google Cloud KMS. It covers the whole API: direct encryption, envelope encryption, the key lifecycle, usage, the audit log, and the console's API tokens, containers, firewalls and billing. It has no dependencies outside the standard library.

go get github.com/x6c-co/rypt

Example

Envelope encryption: rypt generates a data key and wraps it under your rypt key; you encrypt locally with the data key and store the wrapped form beside the data.

client, err := rypt.NewClient(os.Getenv("RYPT_TOKEN"))
if err != nil {
	log.Fatal(err)
}

dk, err := client.GenerateDataKey(ctx, keyID, nil)
if err != nil {
	log.Fatal(err)
}
// Encrypt with dk.Plaintext, for example with AES-256-GCM, store
// dk.Wrapped with the ciphertext, and clear(dk.Plaintext).

dek, err := client.Unwrap(ctx, keyID, dk.Wrapped, nil)
// Decrypt with dek, then clear(dek).

Small values can be encrypted by the service directly:

ciphertext, err := client.Encrypt(ctx, keyID, []byte("078-05-1120"), aad)
plaintext, err := client.Decrypt(ctx, keyID, ciphertext, aad)

Errors from the API carry its error code and request id:

if rypt.IsCode(err, rypt.CodeKeyPendingDeletion) {
	// restore the key with client.RestoreKey
}

Documentation

Versioning

The module follows semantic versioning. Until v1.0.0, a minor version may change the API; see CHANGELOG.md. It supports the Go version in go.mod and later.

Contributing

See CONTRIBUTING.md for the branch workflow, the tests and how releases are made.

License

MIT. See LICENSE.

Documentation

Overview

Package rypt is a client for the rypt API, encryption as a service in front of Google Cloud KMS.

rypt offers two modes. Direct mode encrypts and decrypts payloads of up to 64 KiB with Client.Encrypt and Client.Decrypt. Envelope mode protects data keys: Client.GenerateDataKey returns a new data key together with its wrapped form, the caller encrypts its data locally with the data key and stores the wrapped form next to it, and Client.Unwrap recovers the data key later. Client.Rewrap moves a ciphertext to a key's current primary version after a rotation.

Authentication

Every call except Client.Health sends the token given to NewClient as a bearer token. The API accepts two kinds:

  • An API token, "ry_" followed by a prefix and a secret, issued from the rypt console. It is the only credential the cryptographic operations accept, and it is confined to one container and to the actions it was given.
  • A console session token, an Auth0 access token. It is the only credential that may manage API tokens, containers, firewalls and billing, or destroy a key at once.

The key lifecycle, usage and the audit log accept either. Each method's documentation says which it needs; a call with the wrong kind fails with CodeAPITokenRequired or CodeSessionRequired.

Errors

A call that fails after reaching the network returns an *Error. Its Error.Kind says whether the API refused the request, could not be reached, or answered with a body that does not have the documented shape. When the API refused the request, Error.Code holds the API's stable error code, such as CodeNotFound or CodeKeyPendingDeletion; use IsCode to test for one. All text in an Error that came from the server has had control characters removed and any copy of the token redacted, so it is safe to print and log. Arguments the client can check without the network, such as an id that is not a UUID, fail with an error that is not an *Error.

Retries

A failed request is sent again, up to DefaultMaxAttempts times in all unless WithMaxAttempts says otherwise, when a retry cannot cause harm:

  • Every request is retried after HTTP 503 with the code CodeUnavailable, which the API sends only for a request it did not carry out, and after a failure to connect.
  • Reads, and operations that are safe to repeat, are also retried after a connection that failed part-way and after HTTP 502, 503 and 504. These are the five cryptographic operations, the reads, and the updates and deletions the API treats as idempotent. A retried cryptographic operation may be counted as an operation each time.

The rest — creating anything, rotating, restoring or destroying a key, deleting a container or firewall, and opening billing sessions — is never repeated after the request may have reached the API, because the first attempt may have succeeded. HTTP 429 is never retried: neither a monthly cap nor the rate limit clears in the time a retry waits. Each method's documentation says whether it is retried.

Secrets

Plaintexts and data keys are passed and returned as byte slices so that the caller can zero them with the clear builtin when done. The client builds request bodies that carry them by hand, and clears its own copies of request and response bodies once a call is over. Go does not guarantee that no other copy survives in memory, so this reduces exposure rather than preventing it.

The client refuses to follow redirects, so the token and request bodies are never sent to another URL, and it only talks plain HTTP to a loopback host. See NewClient and WithHTTPClient.

API version

This package is written against version 0.15.0 of the rypt OpenAPI description, a copy of which is in testdata/openapi.json. The API's own documentation is at https://rypt.dev/docs/.

Example

Envelope encryption: a new data key for each record, wrapped by a rypt key. The data is encrypted locally, so it can be of any size, and only the 32-byte data key goes to the service.

package main

import (
	"context"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"fmt"
	"log"
	"os"

	"github.com/x6c-co/rypt"
)

func main() {
	ctx := context.Background()
	client, err := rypt.NewClient(os.Getenv("RYPT_TOKEN"))
	if err != nil {
		log.Fatal(err)
	}
	keyID := os.Getenv("RYPT_KEY_ID")
	record := []byte("customer-42") // binds the wrapped key to this record

	// Encrypt.
	dk, err := client.GenerateDataKey(ctx, keyID, record)
	if err != nil {
		log.Fatal(err)
	}
	block, err := aes.NewCipher(dk.Plaintext)
	clear(dk.Plaintext)
	if err != nil {
		log.Fatal(err)
	}
	aead, err := cipher.NewGCM(block)
	if err != nil {
		log.Fatal(err)
	}
	nonce := make([]byte, aead.NonceSize())
	if _, err := rand.Read(nonce); err != nil {
		log.Fatal(err)
	}
	sealed := aead.Seal(nonce, nonce, []byte("the secret data"), record)
	// Store dk.Wrapped and sealed together.

	// Decrypt.
	dek, err := client.Unwrap(ctx, keyID, dk.Wrapped, record)
	if err != nil {
		log.Fatal(err)
	}
	block, err = aes.NewCipher(dek)
	clear(dek)
	if err != nil {
		log.Fatal(err)
	}
	aead, err = cipher.NewGCM(block)
	if err != nil {
		log.Fatal(err)
	}
	n := aead.NonceSize()
	plaintext, err := aead.Open(nil, sealed[:n], sealed[n:], record)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", plaintext)
}

Index

Examples

Constants

View Source
const (
	// DefaultBaseURL is the production rypt API.
	DefaultBaseURL = "https://api.rypt.dev"

	// DefaultTimeout bounds each HTTP request made by a client that has no
	// [WithHTTPClient] option, including reading the response.
	DefaultTimeout = 30 * time.Second

	// DefaultMaxAttempts is how many times a request is sent at most: one
	// try and up to two retries. See the package documentation for which
	// failures are retried.
	DefaultMaxAttempts = 3
)

Variables

This section is empty.

Functions

func IsCode

func IsCode(err error, code ErrorCode) bool

IsCode reports whether err is, or wraps, an *Error whose Code is code.

Example
package main

import (
	"context"
	"errors"
	"log"
	"os"

	"github.com/x6c-co/rypt"
)

func main() {
	ctx := context.Background()
	client, err := rypt.NewClient(os.Getenv("RYPT_TOKEN"))
	if err != nil {
		log.Fatal(err)
	}
	_, err = client.Encrypt(ctx, os.Getenv("RYPT_KEY_ID"), []byte("data"), nil)
	switch {
	case err == nil:
	case rypt.IsCode(err, rypt.CodeKeyPendingDeletion):
		log.Fatal("the key is pending deletion; restore it to use it")
	case rypt.IsCode(err, rypt.CodeOpsCapReached):
		var e *rypt.Error
		errors.As(err, &e)
		log.Fatalf("the key's monthly cap of %d is reached until %s", e.Cap, e.ResetsAt)
	default:
		log.Fatal(err) // includes the API's request id, for support
	}
}

Types

type APIToken

type APIToken struct {
	// ID is the token's id.
	ID string `json:"id"`
	// Name is the label it was created with, or empty for a token made
	// with the rypt-admin tool.
	Name string `json:"name"`
	// CreatedByUserID is the console user who created it, or empty.
	CreatedByUserID string `json:"created_by_user_id"`
	// CreatedAt is when it was created.
	CreatedAt time.Time `json:"created_at"`
	// RevokedAt is when it was revoked, or nil while it is live.
	RevokedAt *time.Time `json:"revoked_at"`
	// ContainerID is the container it lives in and never sees outside of.
	ContainerID string `json:"container_id"`
	// FirewallID is the firewall it is held to, or empty for none.
	FirewallID string `json:"firewall_id"`
	// KeyIDs are the keys of its container it may act on, ascending, or
	// nil when it may act on all of them. A key destroyed since it was
	// named stays in the list.
	KeyIDs []string `json:"key_ids"`
	// AllowedActions is what it may do with those keys, sorted.
	AllowedActions []Action `json:"allowed_actions"`
}

APIToken is an API token, without its secret.

type Action

type Action string

Action is something an API token may do with the keys it may act on.

const (
	ActionEncrypt Action = "encrypt"
	ActionDecrypt Action = "decrypt"
	ActionWrap    Action = "wrap" // also needed by GenerateDataKey
	ActionUnwrap  Action = "unwrap"
	ActionRewrap  Action = "rewrap"
	ActionRotate  Action = "rotate"
	ActionRename  Action = "rename"
	ActionDelete  Action = "delete"
	ActionRestore Action = "restore"
	// ActionCreate allows creating a key in the token's container. It
	// works only for a token not restricted to particular keys.
	ActionCreate Action = "create"
	// ActionRead allows listing keys, reading a key and its audit log, and
	// reading usage.
	ActionRead Action = "read"
)

The actions an API token can be given.

type AuditEvent

type AuditEvent struct {
	// ID is the event's id, which increases with time.
	ID int64 `json:"id"`
	// Time is when the operation happened, to the microsecond.
	Time time.Time `json:"ts"`
	// KeyID is the key the operation was on.
	KeyID string `json:"key_id"`
	// APITokenID is the API token the request presented, or empty.
	APITokenID string `json:"api_token_id"`
	// UserID is the console user the request authenticated as, or empty.
	UserID string `json:"user_id"`
	// Op is the operation.
	Op AuditOp `json:"op"`
	// KMSVersion is the short id of the key version the operation used, or
	// empty. For decrypt and unwrap it is set only when the ciphertext
	// named the primary version; for destroy_version it is the version
	// destroyed.
	KMSVersion string `json:"kms_version"`
	// Result is whether the operation succeeded.
	Result AuditResult `json:"result"`
	// ErrorCode is the error code of a failed operation, or empty.
	ErrorCode ErrorCode `json:"error_code"`
	// RequestID is the x-request-id of the request that caused the event,
	// or an id the service made for an event it recorded on its own.
	RequestID string `json:"request_id"`
	// SourceIP is the address the request came from, or empty for an
	// event no request caused.
	SourceIP string `json:"src_ip"`
	// Bytes is the size of the operation's input in bytes, never its
	// content, or nil when the request failed before its input was
	// decoded.
	Bytes *int64 `json:"bytes"`
	// KMSMicros is how long the key-management backend took, in
	// microseconds, or nil for an operation that did not call it.
	KMSMicros *int64 `json:"kms_us"`
}

AuditEvent is one entry of a key's audit log: an operation that changed the key or used its material, successful or not.

An event caused by a request names exactly one of APITokenID and UserID. Older events, and those the service records on its own, such as the destruction of a key whose pending deletion ran out, name neither.

type AuditEventsParams

type AuditEventsParams struct {
	// Since is the oldest time to include, inclusive. Zero means no bound.
	Since time.Time
	// Until is the time to stop before, exclusive. Zero means no bound.
	Until time.Time
	// Limit is how many events to return, 1 to 1000. Zero means 100.
	Limit int
}

AuditEventsParams are the parameters of Client.ListAuditEvents. Every field is optional.

type AuditOp

type AuditOp string

AuditOp is the operation an AuditEvent records.

const (
	OpCreateKey      AuditOp = "create_key"
	OpRenameKey      AuditOp = "rename_key"
	OpDeleteKey      AuditOp = "delete_key"  // deletion requested: the key became pending deletion
	OpRestoreKey     AuditOp = "restore_key" // the key was taken out of pending deletion
	OpDestroyKey     AuditOp = "destroy_key" // the key was destroyed, at once or when its pending deletion ran out
	OpRotate         AuditOp = "rotate"
	OpDestroyVersion AuditOp = "destroy_version" // a retired version was destroyed; see AuditEvent.KMSVersion
	OpEncrypt        AuditOp = "encrypt"
	OpDecrypt        AuditOp = "decrypt"
	OpWrap           AuditOp = "wrap"
	OpUnwrap         AuditOp = "unwrap"
	OpRewrap         AuditOp = "rewrap"
)

The operations a key's audit log records.

const (
	OpCreateAPIToken  AuditOp = "create_api_token"
	OpUpdateAPIToken  AuditOp = "update_api_token"
	OpRevokeAPIToken  AuditOp = "revoke_api_token"
	OpCreateContainer AuditOp = "create_container"
	OpRenameContainer AuditOp = "rename_container"
	OpDeleteContainer AuditOp = "delete_container"
	OpCreateFirewall  AuditOp = "create_firewall"
	OpUpdateFirewall  AuditOp = "update_firewall"
	OpDeleteFirewall  AuditOp = "delete_firewall"
)

The operations recorded with no key, which a key's audit log never returns. They are listed so that every value of the API's op enum has a name.

type AuditResult

type AuditResult string

AuditResult is whether an audited operation succeeded.

const (
	ResultOK    AuditResult = "ok"
	ResultError AuditResult = "error"
)

The audit results.

type Billing

type Billing struct {
	// Status is where the subscription stands.
	Status BillingStatus `json:"status"`
	// Period is the UTC month the estimate covers, as "YYYY-MM", always the
	// current one.
	Period string `json:"period"`
	// CancelAt is when the subscription is due to end after a
	// cancellation, or nil when no end is scheduled.
	CancelAt *time.Time `json:"cancel_at"`
	// Estimate is what the month's paid keys and overage come to so far.
	Estimate BillingEstimate `json:"estimate"`
}

Billing is a tenant's billing standing and the current month's estimate. Outside good standing, paid keys still decrypt and unwrap, but encrypt, wrap, rewrap, rotate and creating a paid key fail with CodeBillingRequired.

type BillingEstimate

type BillingEstimate struct {
	// Currency is an ISO 4217 code in lower case, such as "usd".
	Currency string `json:"currency"`
	// TotalCents is the sum of the lines' AmountCents.
	TotalCents int64 `json:"total_cents"`
	// Lines has one line per paid tier, wrapped, software and hsm in that
	// order, whether or not the tenant has a key on it.
	Lines []BillingLine `json:"lines"`
}

BillingEstimate is what a month's paid keys and overage come to so far, before tax. The invoice issued after the month closes is the bill.

type BillingLine

type BillingLine struct {
	// Tier is a paid tier.
	Tier Tier `json:"tier"`
	// KeyMonths is how many of the tenant's keys on the tier are billed
	// for the month.
	KeyMonths int64 `json:"key_months"`
	// KeyDays is the days those keys are billed for so far, summed.
	KeyDays int64 `json:"key_days"`
	// DaysInPeriod is how many days the month has.
	DaysInPeriod int64 `json:"days_in_period"`
	// FeeCents is the tier's fee for one key for a whole month, in cents.
	FeeCents int64 `json:"fee_cents"`
	// OverageOps is the operations beyond the included allowance, summed
	// across the tier's keys.
	OverageOps int64 `json:"overage_ops"`
	// OverageMillicentsPerOp is the price of one overage operation, in
	// thousandths of a cent.
	OverageMillicentsPerOp int64 `json:"overage_millicents_per_op"`
	// FeeAmountCents is what the keys' fees come to, in cents.
	FeeAmountCents int64 `json:"fee_amount_cents"`
	// OverageAmountCents is what the overage comes to, in cents.
	OverageAmountCents int64 `json:"overage_amount_cents"`
	// AmountCents is FeeAmountCents plus OverageAmountCents.
	AmountCents int64 `json:"amount_cents"`
}

BillingLine is one paid tier's part of a BillingEstimate.

type BillingSession

type BillingSession struct {
	// URL is the page's address. It is short-lived and specific to the
	// tenant: follow it at once and never store it.
	URL string `json:"url"`
}

BillingSession is a Stripe-hosted page to send a browser to.

type BillingStatus

type BillingStatus string

BillingStatus is where a tenant's subscription stands.

const (
	// BillingNone is a tenant that has no subscription yet.
	BillingNone BillingStatus = "none"
	// BillingActive is a subscription in good standing.
	BillingActive BillingStatus = "active"
	// BillingPastDue is a subscription whose payment failed and is being
	// retried. It is still in good standing.
	BillingPastDue BillingStatus = "past_due"
	// BillingUnpaid is a subscription whose payment retries ran out, with
	// an unpaid invoice open. Paying it in the portal restores good
	// standing.
	BillingUnpaid BillingStatus = "unpaid"
	// BillingLapsed is a subscription that was cancelled, paused or never
	// completed. A new checkout restores good standing.
	BillingLapsed BillingStatus = "lapsed"
)

The billing statuses.

type ChainHead

type ChainHead struct {
	// Seq is the entry's position; 0 is the chain's first entry.
	Seq int64 `json:"seq"`
	// Hash is the entry's SHA-256 hash, 64 lowercase hex digits.
	Hash string `json:"hash"`
}

ChainHead is the latest entry of a container's permission chain. Every change to what a container's API tokens may do is appended to the chain, and each entry includes the hash of the one before it, so the head's hash stands for the whole history. Kept somewhere the service cannot reach, it is what a rewritten history would fail to match.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client calls the rypt API. It is safe for concurrent use by multiple goroutines. Create one with NewClient and reuse it.

func NewClient

func NewClient(token string, opts ...Option) (*Client, error)

NewClient returns a client that authenticates with token, an API token or a console session token (see the package documentation).

It returns an error if token is empty or contains a character that cannot be sent in an HTTP header, if the base URL is invalid, or if an option has an invalid value. It makes no request.

Example
package main

import (
	"log"
	"os"

	"github.com/x6c-co/rypt"
)

func main() {
	client, err := rypt.NewClient(os.Getenv("RYPT_TOKEN"),
		rypt.WithUserAgent("myapp/1.2.3"),
		rypt.WithMaxAttempts(5),
	)
	if err != nil {
		log.Fatal(err)
	}
	_ = client
}
Example (LocalServer)
package main

import (
	"log"
	"os"

	"github.com/x6c-co/rypt"
)

func main() {
	// Plain http is allowed only to a loopback host.
	client, err := rypt.NewClient(os.Getenv("RYPT_TOKEN"), rypt.WithBaseURL("http://127.0.0.1:8080"))
	if err != nil {
		log.Fatal(err)
	}
	_ = client
}

func (*Client) Billing

func (c *Client) Billing(ctx context.Context) (*Billing, error)

Billing returns the tenant's billing standing and the current month's estimate. Where the deployment does not configure billing it fails with CodeNotFound. It needs a console session.

func (*Client) CreateAPIToken

func (c *Client) CreateAPIToken(ctx context.Context, params *CreateAPITokenParams) (*CreatedAPIToken, error)

CreateAPIToken issues an API token and returns it with its secret, which is shown only this once. Its creation is appended to its container's permission chain. It needs a console session: a token that could issue its successor would outlive its own revocation.

CreateAPIToken is not retried once the request may have reached the API, since a second token could be issued whose secret nobody sees.

func (*Client) CreateCheckoutSession

func (c *Client) CreateCheckoutSession(ctx context.Context) (*BillingSession, error)

CreateCheckoutSession starts a subscription on Stripe Checkout and returns the page to send the browser to. A tenant whose subscription is still open fails with CodeBillingActive; use Client.CreatePortalSession instead. It needs a console session.

Each call makes a new session, so CreateCheckoutSession is not retried once the request may have reached the API.

func (*Client) CreateContainer

func (c *Client) CreateContainer(ctx context.Context, name string) (*Container, error)

CreateContainer makes an empty container. Its name follows the rules of a key's name and is unique among the tenant's live containers, or the call fails with CodeContainerExists. It needs a console session.

CreateContainer is not retried once the request may have reached the API.

func (*Client) CreateFirewall

func (c *Client) CreateFirewall(ctx context.Context, params *CreateFirewallParams) (*Firewall, error)

CreateFirewall makes a firewall. Hold API tokens of its container to it with Client.UpdateAPIToken. It needs a console session.

CreateFirewall is not retried once the request may have reached the API.

func (*Client) CreateKey

func (c *Client) CreateKey(ctx context.Context, params *CreateKeyParams) (*Key, error)

CreateKey creates a key.

With an API token, the token needs the create action and must not be restricted to particular keys. Where billing is configured, a paid tier needs the tenant's billing in good standing, or the call fails with CodeBillingRequired, and a second free key fails with CodeFreeKeyExists. A name in use fails with CodeKeyExists.

CreateKey is not retried once the request may have reached the API.

func (*Client) CreatePortalSession

func (c *Client) CreatePortalSession(ctx context.Context) (*BillingSession, error)

CreatePortalSession opens the Stripe customer portal, where invoices, the payment method and cancellation are managed, and returns the page to send the browser to. A tenant that never started a checkout fails with CodeBillingNotSetUp. It needs a console session.

Each call makes a new session, so CreatePortalSession is not retried once the request may have reached the API.

func (*Client) Decrypt

func (c *Client) Decrypt(ctx context.Context, keyID string, ciphertext, aad []byte) ([]byte, error)

Decrypt decrypts a ciphertext made by Client.Encrypt or Client.Rewrap under any version the key still has, and returns the plaintext. aad must be what the ciphertext was made with, or nil if none was. The caller should clear the plaintext when done with it.

A ciphertext the key did not make, a wrong aad, or a ciphertext under a version that has been destroyed fails with CodeInvalidCiphertext.

The call needs an API token with the decrypt action, and is one metered operation. It is never refused for billing. Decrypt is retried after a transient failure, and each attempt that reaches the key may be counted.

func (*Client) DeleteContainer

func (c *Client) DeleteContainer(ctx context.Context, containerID string) error

DeleteContainer deletes an empty container that is not the default. A container that still holds a key, even one pending deletion, a live API token or a firewall fails with CodeContainerNotEmpty. It needs a console session.

DeleteContainer is not retried once the request may have reached the API: a second attempt would fail with CodeNotFound.

func (*Client) DeleteFirewall

func (c *Client) DeleteFirewall(ctx context.Context, firewallID string) error

DeleteFirewall deletes a firewall. One that a live API token is held to fails with CodeFirewallInUse: lift it from each token with Client.UpdateAPIToken first. It needs a console session.

DeleteFirewall is not retried once the request may have reached the API: a second attempt would fail with CodeNotFound.

func (*Client) DeleteKey

func (c *Client) DeleteKey(ctx context.Context, keyID string) (*Key, error)

DeleteKey puts a key in pending deletion and returns it. From then on the key refuses every operation, decrypt and unwrap included, with CodeKeyPendingDeletion. It is destroyed seven days later, at Key.DestroyAfter, unless it is restored first with Client.RestoreKey. Deleting a paid key stops its billing after the day of the request. An API token needs the delete action.

Deleting a key that is already pending deletion changes nothing and answers the same, so DeleteKey is retried like a read.

func (*Client) DestroyKey

func (c *Client) DestroyKey(ctx context.Context, keyID string) error

DestroyKey destroys a key at once, whether or not it is pending deletion. This cannot be undone: anything encrypted under the key can never be decrypted again, and from then on the key is CodeNotFound. It needs a console session; an API token fails with CodeSessionRequired.

DestroyKey is not retried once the request may have reached the API.

func (*Client) Encrypt

func (c *Client) Encrypt(ctx context.Context, keyID string, plaintext, aad []byte) ([]byte, error)

Encrypt encrypts plaintext under a key's primary version and returns the ciphertext. The ciphertext names the version it was made under, so Client.Decrypt keeps working after a rotation while that version is kept.

plaintext is 1 to 65,536 bytes; on the hsm tier, plaintext and aad together are at most 8,176 bytes. aad, the additional authenticated data, may be nil; when it is not, the same aad must be given to Decrypt and Rewrap. aad is at most 65,520 bytes on the software and hsm tiers and 65,536 on the others.

The call needs an API token with the encrypt action, and is one metered operation. Where billing is configured, a paid key whose tenant's billing has lapsed fails with CodeBillingRequired. Encrypt is retried after a transient failure, and each attempt that reaches the key may be counted.

Example

Direct encryption of a small payload: the service encrypts it, and the ciphertext names the key version, so it decrypts after a rotation too.

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/x6c-co/rypt"
)

func main() {
	ctx := context.Background()
	client, err := rypt.NewClient(os.Getenv("RYPT_TOKEN"))
	if err != nil {
		log.Fatal(err)
	}
	keyID := os.Getenv("RYPT_KEY_ID")
	aad := []byte("table=users;column=ssn")

	ciphertext, err := client.Encrypt(ctx, keyID, []byte("078-05-1120"), aad)
	if err != nil {
		log.Fatal(err)
	}
	plaintext, err := client.Decrypt(ctx, keyID, ciphertext, aad)
	if err != nil {
		log.Fatal(err)
	}
	defer clear(plaintext)
	fmt.Printf("%s\n", plaintext)
}

func (*Client) GenerateDataKey

func (c *Client) GenerateDataKey(ctx context.Context, keyID string, aad []byte) (*DataKey, error)

GenerateDataKey asks the service to generate a random 32-byte data key, and returns it together with its wrapped form. This is the usual first step of envelope encryption: encrypt the data locally with DataKey.Plaintext, for example with AES-256-GCM, store DataKey.Wrapped with it, and clear the data key.

aad may be nil. When it is not, it binds the wrapped key to its context, such as the id of the record it protects, and the same aad must be given to Client.Unwrap.

The call needs an API token with the wrap action, and is one metered operation. Where billing is configured, a paid key whose tenant's billing has lapsed fails with CodeBillingRequired. GenerateDataKey is retried after a transient failure, and each attempt that reaches the key may be counted.

func (*Client) GetKey

func (c *Client) GetKey(ctx context.Context, keyID string) (*Key, error)

GetKey returns a key, including one pending deletion. A destroyed key fails with CodeNotFound. An API token needs the read action. It is not metered.

func (*Client) Health

func (c *Client) Health(ctx context.Context) error

Health checks that the API is up and serving. It sends no token, and returns nil when the API answers that it is.

func (*Client) ListAPITokens

func (c *Client) ListAPITokens(ctx context.Context) ([]APIToken, error)

ListAPITokens returns every API token of the tenant, newest first, including revoked ones. Neither a token's secret nor its prefix is ever returned. It needs a console session.

func (*Client) ListAuditEvents

func (c *Client) ListAuditEvents(ctx context.Context, keyID string, params *AuditEventsParams) ([]AuditEvent, error)

ListAuditEvents returns a key's audit events, newest first. It works for keys pending deletion and for destroyed keys. params may be nil.

Since and Until select a window rather than acting as a cursor, so choose a Limit that covers the window you ask for. An event's Time round-trips exactly as a bound.

An API token needs the read action. Reading the log is not metered and does not add to it.

Example
package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/x6c-co/rypt"
)

func main() {
	ctx := context.Background()
	client, err := rypt.NewClient(os.Getenv("RYPT_TOKEN"))
	if err != nil {
		log.Fatal(err)
	}
	events, err := client.ListAuditEvents(ctx, os.Getenv("RYPT_KEY_ID"), &rypt.AuditEventsParams{
		Since: time.Now().Add(-24 * time.Hour),
		Limit: 1000,
	})
	if err != nil {
		log.Fatal(err)
	}
	for _, ev := range events {
		fmt.Println(ev.Time.Format(time.RFC3339), ev.Op, ev.Result, ev.ErrorCode)
	}
}

func (*Client) ListContainers

func (c *Client) ListContainers(ctx context.Context) ([]Container, error)

ListContainers returns the tenant's live containers, the default first and the rest oldest first. It needs a console session.

func (*Client) ListFirewalls

func (c *Client) ListFirewalls(ctx context.Context) ([]Firewall, error)

ListFirewalls returns the tenant's live firewalls in every container, oldest first. It needs a console session.

func (*Client) ListKeys

func (c *Client) ListKeys(ctx context.Context) ([]Key, error)

ListKeys returns the keys the caller may see, oldest first, including those pending deletion: for a console session the whole tenant's, for an API token its container's, and for one restricted to particular keys only those. An API token needs the read action. It is not metered.

func (*Client) Me

func (c *Client) Me(ctx context.Context) (*Me, error)

Me returns the console session's user, tenant and role. The first call for a user the service has not seen provisions the user and a tenant they own. It needs a console session.

func (*Client) PermissionChain

func (c *Client) PermissionChain(ctx context.Context, containerID string) (*PermissionChain, error)

PermissionChain returns the head of a container's permission chain. The first read of a container's head may start its chain. It needs a console session.

func (*Client) RenameContainer

func (c *Client) RenameContainer(ctx context.Context, containerID, name string) (*Container, error)

RenameContainer renames a container, the default one included, and returns it. A name another live container has fails with CodeContainerExists. It needs a console session.

Renaming a container to the name it has changes nothing, so RenameContainer is retried like a read.

func (*Client) RenameKey

func (c *Client) RenameKey(ctx context.Context, keyID, name string) (*Key, error)

RenameKey gives a key a new name, held to the rules of CreateKeyParams.Name, and returns the key. Nothing else about the key changes. An API token needs the rename action. A key pending deletion fails with CodeKeyPendingDeletion, and a name in use with CodeKeyExists.

Renaming a key to the name it has changes nothing, so RenameKey is retried like a read.

func (*Client) RestoreKey

func (c *Client) RestoreKey(ctx context.Context, keyID string) (*Key, error)

RestoreKey takes a key out of pending deletion and returns it, active again. The key is then billed as if it had never been deleted. An API token needs the restore action. A key that is not pending deletion fails with CodeNotPendingDeletion, and one whose Key.DestroyAfter has passed with CodeRestoreWindowClosed.

RestoreKey is not retried once the request may have reached the API.

func (*Client) RevokeAPIToken

func (c *Client) RevokeAPIToken(ctx context.Context, tokenID string) error

RevokeAPIToken revokes an API token. It stops working from its next request. Revoking a token that is already revoked succeeds. It needs a console session.

RevokeAPIToken is retried like a read.

func (*Client) Rewrap

func (c *Client) Rewrap(ctx context.Context, keyID string, ciphertext, aad []byte) ([]byte, error)

Rewrap decrypts a ciphertext made by Client.Encrypt or Rewrap under whatever version it names, and encrypts the plaintext again under the key's primary version, without the plaintext leaving the service. aad must be what the ciphertext was made with, or nil; it is applied to the new ciphertext too. Use it after Client.RotateKey to move ciphertext off a retiring version.

The call needs an API token with the rewrap action, and is one metered operation. Where billing is configured, a paid key whose tenant's billing has lapsed fails with CodeBillingRequired. Rewrap is retried after a transient failure, and each attempt that reaches the key may be counted.

func (*Client) RotateKey

func (c *Client) RotateKey(ctx context.Context, keyID string) (*Key, error)

RotateKey creates a new version of a key, makes it primary, and returns the key. Versions beyond the tier's retained count (free 1, wrapped 1, software 3, hsm 2) are retired: each keeps decrypting for seven days and is listed in Key.RetiringVersions. On the free and wrapped tiers every rotation retires the previous version, so rewrap everything under it within seven days. An API token needs the rotate action.

A key with ten versions retiring fails with CodeTooManyRetiringVersions. Where billing is configured, a paid key whose tenant's billing has lapsed fails with CodeBillingRequired, and a free key at its monthly cap with CodeOpsCapReached.

RotateKey is not retried once the request may have reached the API: a second rotation would retire another version.

Example

After a rotation, move every ciphertext under the retired version to the new primary version before the retired one is destroyed.

package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/x6c-co/rypt"
)

func main() {
	ctx := context.Background()
	client, err := rypt.NewClient(os.Getenv("RYPT_TOKEN"))
	if err != nil {
		log.Fatal(err)
	}
	keyID := os.Getenv("RYPT_KEY_ID")
	var stored [][]byte // ciphertexts made by Encrypt or Rewrap

	key, err := client.RotateKey(ctx, keyID)
	if err != nil {
		log.Fatal(err)
	}
	for _, v := range key.RetiringVersions {
		fmt.Printf("version %s is destroyed after %s\n", v.Version, v.DestroyAfter.Format(time.RFC3339))
	}
	for i, ct := range stored {
		if stored[i], err = client.Rewrap(ctx, keyID, ct, nil); err != nil {
			log.Fatal(err)
		}
	}
}

func (*Client) Unwrap

func (c *Client) Unwrap(ctx context.Context, keyID string, wrapped, aad []byte) ([]byte, error)

Unwrap recovers a data key from the wrapped form that Client.GenerateDataKey or Client.Wrap returned. aad must be what the key was wrapped with, or nil if none was; a different one fails with CodeInvalidCiphertext. The caller should clear the data key when done with it.

The call needs an API token with the unwrap action, and is one metered operation. It is never refused for billing. Unwrap is retried after a transient failure, and each attempt that reaches the key may be counted.

func (*Client) UpdateAPIToken

func (c *Client) UpdateAPIToken(ctx context.Context, tokenID string, params *UpdateAPITokenParams) (*UpdatedAPIToken, error)

UpdateAPIToken changes what an API token may do: the firewall it is held to, the keys it may act on, and its actions. The change applies from the token's next request. A change to its keys or actions is appended to its container's permission chain. It needs a console session.

The update sets the token's state rather than changing it step by step, so UpdateAPIToken is retried like a read.

Example

Restrict an API token to one key and two actions, and lift its firewall. Fields left out of the params are not changed.

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/x6c-co/rypt"
)

func main() {
	ctx := context.Background()
	// Managing API tokens needs a console session's access token.
	client, err := rypt.NewClient(os.Getenv("RYPT_CONSOLE_TOKEN"))
	if err != nil {
		log.Fatal(err)
	}
	token, err := client.UpdateAPIToken(ctx, "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d", &rypt.UpdateAPITokenParams{
		KeyIDs:         rypt.Set([]string{"0f1e2d3c-4b5a-6978-8796-a5b4c3d2e1f0"}),
		AllowedActions: rypt.Set([]rypt.Action{rypt.ActionWrap, rypt.ActionUnwrap}),
		FirewallID:     rypt.Clear[string](),
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(token.AllowedActions, token.PermissionChainHead.Hash)
}

func (*Client) UpdateFirewall

func (c *Client) UpdateFirewall(ctx context.Context, firewallID string, params *UpdateFirewallParams) (*Firewall, error)

UpdateFirewall renames a firewall, replaces its networks, or both, and returns it. New networks apply to every API token held to it from the token's next request. It needs a console session.

The update sets the firewall's state rather than changing it step by step, so UpdateFirewall is retried like a read.

func (*Client) Usage

func (c *Client) Usage(ctx context.Context, period string) ([]UsageLine, error)

Usage returns the usage report for a calendar month in UTC: one line per live key, including those pending deletion and those that did nothing, and one per destroyed key that performed operations in the month. An API token sees its container's lines, or only its keys' lines if it is restricted to particular keys, and needs the read action.

period is the month as "YYYY-MM", or empty for the current month. Reading usage is not metered.

func (*Client) Wrap

func (c *Client) Wrap(ctx context.Context, keyID string, dek, aad []byte) ([]byte, error)

Wrap encrypts a data key the caller made under a rypt key, and returns the wrapped form. dek must be 16, 24 or 32 bytes. To have the service generate the data key instead, use Client.GenerateDataKey.

aad may be nil. When it is not, it binds the wrapped key to its context, and the same aad must be given to Client.Unwrap.

The call needs an API token with the wrap action, and is one metered operation. Where billing is configured, a paid key whose tenant's billing has lapsed fails with CodeBillingRequired. Wrap is retried after a transient failure, and each attempt that reaches the key may be counted.

type Container

type Container struct {
	// ID is the container's id.
	ID string `json:"id"`
	// Name is unique among the tenant's live containers.
	Name string `json:"name"`
	// IsDefault reports whether this is the tenant's default container,
	// which everything goes in unless told otherwise and which cannot be
	// deleted.
	IsDefault bool `json:"is_default"`
	// CreatedAt is when it was created.
	CreatedAt time.Time `json:"created_at"`
}

Container holds keys, API tokens and firewalls. An API token never sees outside its own container. Every tenant has one default container.

type CreateAPITokenParams

type CreateAPITokenParams struct {
	// Name is a label that tells the token apart from the tenant's others:
	// 1 to 128 characters, not only whitespace, with no control
	// characters. Required.
	Name string `json:"name"`
	// ContainerID is the container the token lives in, for good. Empty
	// means the tenant's default container.
	ContainerID string `json:"container_id,omitempty"`
	// AllowedActions is what the token may do with its container's keys.
	// Empty allows nothing.
	AllowedActions []Action `json:"allowed_actions,omitempty"`
}

CreateAPITokenParams are the parameters of Client.CreateAPIToken.

type CreateFirewallParams

type CreateFirewallParams struct {
	// ContainerID is the container it goes in. Empty means the tenant's
	// default container.
	ContainerID string `json:"container_id,omitempty"`
	// Name follows the rules of a key's name and is unique among its
	// container's live firewalls. Required.
	Name string `json:"name"`
	// AllowedIPs are 1 to 64 addresses or networks in CIDR notation.
	// "0.0.0.0/0" and "::/0" allow a whole address family. A network with
	// bits set past its prefix, such as "203.0.113.5/24", an IPv4 address
	// written as IPv6, and surrounding whitespace fail with
	// [CodeInvalidAllowedIPs]. Required.
	AllowedIPs []string `json:"allowed_ips"`
}

CreateFirewallParams are the parameters of Client.CreateFirewall.

type CreateKeyParams

type CreateKeyParams struct {
	// Name is 1 to 128 characters, not only whitespace, with no control
	// characters, and unique among the container's live keys, including
	// those pending deletion. Required.
	Name string `json:"name"`
	// Tier is the key's tier. Required.
	Tier Tier `json:"tier"`
	// ContainerID is the container the key goes in. Empty means the API
	// token's own container, or the tenant's default container for a
	// console session. An API token cannot name another container.
	ContainerID string `json:"container_id,omitempty"`
}

CreateKeyParams are the parameters of Client.CreateKey.

type CreatedAPIToken

type CreatedAPIToken struct {
	// ID is the token's id.
	ID string `json:"id"`
	// Name is the label it was created with.
	Name string `json:"name"`
	// ContainerID is the container it lives in.
	ContainerID string `json:"container_id"`
	// Prefix is the token's 8-character lookup prefix, which is part of
	// Token. It is returned here and nowhere else.
	Prefix string `json:"prefix"`
	// CreatedAt is when it was created.
	CreatedAt time.Time `json:"created_at"`
	// AllowedActions is what it may do, sorted.
	AllowedActions []Action `json:"allowed_actions"`
	// PermissionChainHead is the head of its container's permission chain
	// after the creation was appended to it.
	PermissionChainHead ChainHead `json:"permission_chain_head"`
	// Token is the API token itself, to pass to [NewClient]. It is
	// returned exactly once and cannot be recovered: store it now.
	Token string `json:"token"`
}

CreatedAPIToken is an API token just created, with its secret.

type DataKey

type DataKey struct {
	// Plaintext is the 32-byte data key, to encrypt data with locally. The
	// caller should clear it when done with it and never store it.
	Plaintext []byte
	// Wrapped is the data key encrypted under the rypt key. Store it next
	// to the data, and recover the data key from it with [Client.Unwrap].
	Wrapped []byte
}

DataKey is a data key generated by Client.GenerateDataKey.

type Error

type Error struct {
	// Kind says what went wrong.
	Kind ErrorKind
	// StatusCode is the HTTP status of the last response, or 0 when no
	// response arrived.
	StatusCode int
	// Code is the API's machine-readable error code, or empty when the
	// response did not carry the API's error envelope, as with an error
	// page from a proxy.
	Code ErrorCode
	// Message is the API's human-readable message, or a description of the
	// failure when the API did not send one.
	Message string
	// RequestID identifies the last request in the API's logs. It is the
	// error body's request_id, or else the x-request-id response header.
	RequestID string
	// Cap is the key tier's monthly operation cap, for CodeOpsCapReached.
	Cap int64
	// ResetsAt is when the monthly operation counter resets, for
	// CodeOpsCapReached.
	ResetsAt time.Time
	// Attempts is how many requests were sent.
	Attempts int
	// Err is the underlying error of a KindTransport error.
	Err error
}

Error is the error a call returns when it fails after making a request, unless the call's context ended first, in which case the call returns the context's error.

All text in an Error that came from the server has had control and invisible formatting characters removed, is cut to a few hundred bytes, and has any copy of the client's token replaced with "[redacted]".

Example
package main

import (
	"context"
	"errors"
	"log"
	"os"

	"github.com/x6c-co/rypt"
)

func main() {
	ctx := context.Background()
	client, err := rypt.NewClient(os.Getenv("RYPT_TOKEN"))
	if err != nil {
		log.Fatal(err)
	}
	_, err = client.ListKeys(ctx)
	var e *rypt.Error
	if errors.As(err, &e) {
		switch e.Kind {
		case rypt.KindAPI:
			log.Printf("refused with %s (HTTP %d), request %s", e.Code, e.StatusCode, e.RequestID)
		case rypt.KindTransport:
			log.Printf("unreachable after %d attempts: %v", e.Attempts, e.Err)
		case rypt.KindResponse:
			log.Printf("unexpected response: %s", e.Message)
		}
	}
}

func (*Error) Error

func (e *Error) Error() string

Error returns a one-line description that includes the API's error code, message and request id when there are any.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns e.Err.

type ErrorCode

type ErrorCode string

ErrorCode is one of the API's stable, machine-readable error codes.

const (
	CodeInvalidRequest        ErrorCode = "invalid_request"         // the body or a parameter is malformed, or names an unknown field
	CodeInvalidBase64         ErrorCode = "invalid_base64"          // a binary field is not standard padded base64
	CodeInvalidTier           ErrorCode = "invalid_tier"            // the tier is not one of the four
	CodeInvalidName           ErrorCode = "invalid_name"            // a name is empty, too long, only whitespace, or has control characters
	CodeInvalidDEKLength      ErrorCode = "invalid_dek_length"      // a data key to wrap is not 16, 24 or 32 bytes
	CodeInvalidCiphertext     ErrorCode = "invalid_ciphertext"      // the ciphertext was refused, the aad does not match, or its version is gone
	CodeInvalidPeriod         ErrorCode = "invalid_period"          // a usage period is not YYYY-MM
	CodeInvalidQuery          ErrorCode = "invalid_query"           // a query parameter is invalid
	CodeInvalidAllowedIPs     ErrorCode = "invalid_allowed_ips"     // a firewall network is invalid
	CodeInvalidKeyIDs         ErrorCode = "invalid_key_ids"         // a key list is empty or names a key outside the container
	CodeInvalidAllowedActions ErrorCode = "invalid_allowed_actions" // an action list is null or names an unknown action
	CodeInvalidContainer      ErrorCode = "invalid_container"       // the container is not one the caller may use
	CodeInvalidFirewall       ErrorCode = "invalid_firewall"        // the firewall is not a live one of the container

	CodeUnauthorized ErrorCode = "unauthorized" // the token is missing, malformed, unknown, revoked or wrong

	CodeBillingRequired ErrorCode = "billing_required" // the tenant's billing is not in good standing

	CodeAPITokenRequired ErrorCode = "api_token_required" // the endpoint needs an API token, not a console session
	CodeSessionRequired  ErrorCode = "session_required"   // the endpoint needs a console session, not an API token
	CodeIPNotAllowed     ErrorCode = "ip_not_allowed"     // the API token was used from outside its firewall's networks
	CodeKeyNotAllowed    ErrorCode = "key_not_allowed"    // the API token is restricted to other keys
	CodeActionNotAllowed ErrorCode = "action_not_allowed" // the API token was not given this action
	CodeNoTenant         ErrorCode = "no_tenant"          // the user belongs to no tenant

	CodeNotFound         ErrorCode = "not_found"          // the route or id is unknown, destroyed, or not the caller's to see
	CodeMethodNotAllowed ErrorCode = "method_not_allowed" // the route does not take this method

	CodeKeyExists               ErrorCode = "key_exists"                 // another live key in the container has the name
	CodeFreeKeyExists           ErrorCode = "free_key_exists"            // the tenant already has its one free key
	CodeKeyPendingDeletion      ErrorCode = "key_pending_deletion"       // the key is pending deletion and refuses every operation
	CodeTooManyRetiringVersions ErrorCode = "too_many_retiring_versions" // the key already has ten versions retiring
	CodeNotPendingDeletion      ErrorCode = "not_pending_deletion"       // the key to restore is not pending deletion
	CodeRestoreWindowClosed     ErrorCode = "restore_window_closed"      // the key is being destroyed and can no longer be restored
	CodeContainerExists         ErrorCode = "container_exists"           // another live container has the name
	CodeFirewallExists          ErrorCode = "firewall_exists"            // another live firewall in the container has the name
	CodeContainerNotEmpty       ErrorCode = "container_not_empty"        // the container still holds keys, API tokens or firewalls
	CodeContainerIsDefault      ErrorCode = "container_is_default"       // the default container cannot be deleted
	CodeFirewallInUse           ErrorCode = "firewall_in_use"            // live API tokens are held to the firewall
	CodeBillingActive           ErrorCode = "billing_active"             // the tenant already has an open subscription
	CodeBillingNotSetUp         ErrorCode = "billing_not_set_up"         // the tenant has never started a checkout

	CodePayloadTooLarge      ErrorCode = "payload_too_large"      // the body or a field is over its limit
	CodeUnsupportedMediaType ErrorCode = "unsupported_media_type" // the body is not sent as application/json

	CodeOpsCapReached ErrorCode = "ops_cap_reached" // a free key is at its monthly cap; see Error.Cap and Error.ResetsAt
	CodeRateLimited   ErrorCode = "rate_limited"    // over 100 requests per second for this API token

	CodeInternalError ErrorCode = "internal_error" // the service failed; the cause is logged with the request id
	CodeUnavailable   ErrorCode = "unavailable"    // the service did not carry out the request; it is safe to retry
)

The API's error codes, by HTTP status.

type ErrorKind

type ErrorKind int

ErrorKind classifies an Error.

const (
	// KindAPI means the API answered with an error status. Code holds the
	// API's error code when the body was the API's error envelope.
	KindAPI ErrorKind = iota + 1
	// KindTransport means the API could not be reached, or the connection
	// failed before a complete response arrived. StatusCode is 0.
	KindTransport
	// KindResponse means the API answered with a success status but a body
	// that does not have the documented shape.
	KindResponse
)

func (ErrorKind) String

func (k ErrorKind) String() string

String returns "api", "transport" or "response".

type Firewall

type Firewall struct {
	// ID is the firewall's id.
	ID string `json:"id"`
	// ContainerID is the container it lives in. Only that container's API
	// tokens can be held to it.
	ContainerID string `json:"container_id"`
	// Name is unique among its container's live firewalls.
	Name string `json:"name"`
	// AllowedIPs are networks in CIDR notation, ascending. An address
	// given alone is stored as a /32 or /128 network.
	AllowedIPs []string `json:"allowed_ips"`
	// CreatedAt is when it was created.
	CreatedAt time.Time `json:"created_at"`
}

Firewall is a named list of networks that API tokens of its container can be held to. A token used from outside its firewall's networks fails with CodeIPNotAllowed.

type Key

type Key struct {
	// ID is the key's id, which every key operation takes.
	ID string `json:"id"`
	// ContainerID is the container the key lives in, fixed at creation.
	ContainerID string `json:"container_id"`
	// Name is unique among its container's live keys.
	Name string `json:"name"`
	// Tier is fixed at creation.
	Tier Tier `json:"tier"`
	// Versions are the short ids of the key's enabled versions, ascending.
	Versions []string `json:"versions"`
	// PrimaryVersion is the version new ciphertext is made under. It is
	// always one of Versions.
	PrimaryVersion string `json:"primary_version"`
	// RetiringVersions are the versions a rotation retired, ascending,
	// each with the time it is destroyed. Each is also in Versions.
	RetiringVersions []RetiringVersion `json:"retiring_versions"`
	// CreatedAt is when the key was created.
	CreatedAt time.Time `json:"created_at"`
	// State is whether the key is active or pending deletion.
	State KeyState `json:"state"`
	// DeleteRequestedAt is when deletion was requested, or nil unless the
	// key is pending deletion.
	DeleteRequestedAt *time.Time `json:"delete_requested_at"`
	// DestroyAfter is when a key pending deletion is destroyed unless it
	// is restored first, seven days after DeleteRequestedAt, or nil unless
	// the key is pending deletion. From this time on it can no longer be
	// restored, though it may be listed for a while longer.
	DestroyAfter *time.Time `json:"destroy_after"`
}

Key is a rypt key.

type KeyState

type KeyState string

KeyState is whether a key is usable.

const (
	// KeyActive is a key that can be used.
	KeyActive KeyState = "active"
	// KeyPendingDeletion is a key that was deleted and is not yet
	// destroyed. It refuses every operation with [CodeKeyPendingDeletion]
	// until it is restored with [Client.RestoreKey].
	KeyPendingDeletion KeyState = "pending_deletion"
)

The key states.

type Me

type Me struct {
	// User is the signed-in user.
	User User `json:"user"`
	// Tenant is the tenant every other call is scoped to.
	Tenant Tenant `json:"tenant"`
	// Role is the user's role in that tenant.
	Role Role `json:"role"`
}

Me is the signed-in console user and the tenant they act as.

type Option

type Option func(*options)

An Option configures a Client. Pass options to NewClient.

func WithBackoff

func WithBackoff(backoff func(retry int) time.Duration) Option

WithBackoff sets the delay before each retry. backoff is called with 1 before the first retry, 2 before the second, and so on. The default waits up to 250ms before the first retry and twice as long before each later one, to at most 5s, with random jitter that shortens each delay by up to half.

func WithBaseURL

func WithBaseURL(baseURL string) Option

WithBaseURL sets the API base URL. The default is DefaultBaseURL.

The URL must use https, except that http is allowed when the host is localhost, 127.0.0.1 or ::1, for a server under development. It must have a host and no user info, query or fragment. It may have a path, which then prefixes every API path.

func WithHTTPClient

func WithHTTPClient(client *http.Client) Option

WithHTTPClient sets the HTTP client that sends requests, for example to add instrumentation to its transport or to change its timeout.

The client is copied, and the copy refuses redirects whatever the original's CheckRedirect says, so that the token is never sent to another URL. The default client also never sends plain-HTTP requests through a proxy, where the token would travel in clear text; a transport given here is responsible for its own proxy settings.

func WithMaxAttempts

func WithMaxAttempts(n int) Option

WithMaxAttempts sets how many times a request is sent at most, counting the first try. 1 turns retries off. The default is DefaultMaxAttempts.

func WithUserAgent

func WithUserAgent(userAgent string) Option

WithUserAgent names the application in the User-Agent header, for example "myapp/1.2.3". The client appends its own name and version.

type Patch

type Patch[T any] struct {
	// contains filtered or unexported fields
}

Patch is one field of an update request, which can leave the field as it is, replace its value, or clear it. The zero Patch leaves the field as it is; Set makes one that replaces it, and Clear one that clears it, which the API receives as JSON null.

A Patch of a slice type set to a nil slice is sent as an empty list, not as null, so that a nil slice can never clear a restriction by accident.

func Clear

func Clear[T any]() Patch[T]

Clear returns a Patch that clears a field, sending JSON null.

func Set

func Set[T any](v T) Patch[T]

Set returns a Patch that replaces a field's value with v.

func (Patch[T]) IsClear

func (p Patch[T]) IsClear() bool

IsClear reports whether p clears its field.

func (Patch[T]) IsZero

func (p Patch[T]) IsZero() bool

IsZero reports whether p leaves its field as it is. encoding/json uses it to leave the field out of a request.

func (Patch[T]) MarshalJSON

func (p Patch[T]) MarshalJSON() ([]byte, error)

MarshalJSON encodes the value p sets, or null when p clears its field or leaves it as it is. A field left as it is should be omitted with the omitzero option rather than encoded.

func (Patch[T]) Value

func (p Patch[T]) Value() (T, bool)

Value returns the value p sets and true, or the zero value and false when p does not set one.

type PermissionChain

type PermissionChain struct {
	// ContainerID is the container whose chain this is.
	ContainerID string `json:"container_id"`
	// Head is the chain's latest entry.
	Head ChainHead `json:"head"`
}

PermissionChain is the head of a container's permission chain. See ChainHead.

type RetiringVersion

type RetiringVersion struct {
	// Version is the version's short id.
	Version string `json:"version"`
	// DestroyAfter is when the version is destroyed, seven days after the
	// rotation that retired it.
	DestroyAfter time.Time `json:"destroy_after"`
}

RetiringVersion is a key version that a rotation retired. Until DestroyAfter it still decrypts, unwraps and rewraps; after that, anything still under it can never be decrypted again.

type Role

type Role string

Role is a user's role in a tenant.

const (
	RoleOwner  Role = "owner"
	RoleAdmin  Role = "admin"
	RoleMember Role = "member"
)

The roles.

type Tenant

type Tenant struct {
	// ID is the tenant's id.
	ID string `json:"id"`
	// Name is the tenant's name.
	Name string `json:"name"`
}

Tenant is a rypt tenant.

type Tier

type Tier string

Tier is a key's tier. It decides the Cloud KMS protection level, how many versions a rotation keeps, how many operations a month are included, and the price. A key's tier is fixed when it is created.

const (
	// TierFree is a wrapped key with a hard monthly cap of 10,000
	// operations. A tenant may hold one live free key.
	TierFree Tier = "free"
	// TierWrapped is the free tier's backend without its cap: a rypt data
	// key wrapped by a shared Cloud KMS root. Paid.
	TierWrapped Tier = "wrapped"
	// TierSoftware is a Cloud KMS key at SOFTWARE protection level. Paid.
	TierSoftware Tier = "software"
	// TierHSM is a Cloud KMS key at HSM protection level. Paid. Its
	// plaintext (or data key) and aad may total at most 8,176 bytes.
	TierHSM Tier = "hsm"
)

The four tiers.

type UpdateAPITokenParams

type UpdateAPITokenParams struct {
	// FirewallID holds the token to a live firewall of its container, or
	// with Clear lifts its firewall.
	FirewallID Patch[string] `json:"firewall_id,omitzero"`
	// KeyIDs restricts the token to 1 to 100 live keys of its container,
	// or with Clear lets it act on every key of its container. An empty
	// list fails with [CodeInvalidKeyIDs]: revoke the token instead.
	KeyIDs Patch[[]string] `json:"key_ids,omitzero"`
	// AllowedActions replaces what the token may do; an empty list allows
	// nothing. It cannot be cleared: Clear fails with
	// [CodeInvalidAllowedActions].
	AllowedActions Patch[[]Action] `json:"allowed_actions,omitzero"`
}

UpdateAPITokenParams are the parameters of Client.UpdateAPIToken. A field left as its zero value is not changed.

type UpdateFirewallParams

type UpdateFirewallParams struct {
	// Name is the firewall's new name.
	Name string `json:"name,omitempty"`
	// AllowedIPs replaces the firewall's networks, checked as in
	// [CreateFirewallParams]. An empty list is left out of the request.
	AllowedIPs []string `json:"allowed_ips,omitempty"`
}

UpdateFirewallParams are the parameters of Client.UpdateFirewall. A field left as its zero value is not changed.

type UpdatedAPIToken

type UpdatedAPIToken struct {
	APIToken
	// PermissionChainHead is the head of the token's container's
	// permission chain after the update, whether or not it moved.
	PermissionChainHead ChainHead `json:"permission_chain_head"`
}

UpdatedAPIToken is an API token as an update left it.

type UsageLine

type UsageLine struct {
	// KeyID is the key.
	KeyID string `json:"key_id"`
	// ContainerID is the container the key lives in, so that the line of a
	// destroyed key can still be placed.
	ContainerID string `json:"container_id"`
	// Ops is the operations performed in the month, including any past the
	// allowance.
	Ops int64 `json:"ops"`
	// OverageOps is how many of Ops were past Allowance. On a paid tier it
	// is what overage is billed on. On the free tier, which refuses rather
	// than allowing overage, it stays 0 but for a few operations that
	// concurrent requests can carry past the cap, which are never billed.
	OverageOps int64 `json:"overage_ops"`
	// Cap is the tier's monthly cap: a hard limit on the free tier, and the
	// included allowance of a paid key that exists all month.
	Cap int64 `json:"cap"`
	// Allowance is the key's included operations for the month. It is Cap,
	// except for a paid key from October 2026 on, whose allowance is split
	// by the days it exists in the month, rounded down.
	Allowance int64 `json:"allowance"`
}

UsageLine is one key's usage in a month.

type User

type User struct {
	// ID is the user's id, which audit events record as UserID.
	ID string `json:"id"`
	// Email is the email the access token last carried, or empty. Auth0
	// puts none in an access token by default.
	Email string `json:"email"`
	// Name is the name the access token last carried, or empty.
	Name string `json:"name"`
	// CreatedAt is when the user first authenticated.
	CreatedAt time.Time `json:"created_at"`
}

User is a console user.

Directories

Path Synopsis
examples
direct command
Command direct encrypts a short message with the rypt service and decrypts it again.
Command direct encrypts a short message with the rypt service and decrypts it again.
envelope command
Command envelope encrypts a file with envelope encryption and decrypts it again: a new data key from rypt encrypts the file locally with AES-256-GCM, and only the data key's wrapped form is kept, next to the ciphertext.
Command envelope encrypts a file with envelope encryption and decrypts it again: a new data key from rypt encrypts the file locally with AES-256-GCM, and only the data key's wrapped form is kept, next to the ciphertext.
usage command
Command usage prints this month's usage of every key an API token can see, next to each key's name and tier.
Command usage prints this month's usage of every key an API token can see, next to each key's name and tier.

Jump to

Keyboard shortcuts

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