Documentation
¶
Overview ¶
Package protect signs or encrypts agent YAML shipped in OCI artifacts and verifies it on the way back.
A key is either a symmetric secret or an asymmetric key (private, or public-only). The kind is detected from the file contents: PEM-encoded (RFC 7468) or OpenSSH keys are asymmetric; anything else is a raw secret.
The YAML always stays in clear in the artifact layer. Depending on the Mode chosen by the publisher, the manifest annotations carry a signature (MAC or digital signature) and optionally an authenticated encrypted copy of the whole YAML that key holders can decrypt.
Security model ¶
Verification answers "was this produced by a holder of the key?". For a symmetric secret, both the MAC and the AEAD ciphertext require the secret, so either annotation alone is proof. For an asymmetric key, only a signature proves possession of the private key: anyone holding the public key can encrypt. Encrypt mode with an asymmetric key therefore requires the private key and records both a signature and an encrypted copy, and verification with an asymmetric key always requires a signature. This also rules out downgrading a signed artifact to an encrypted-only one.
Signatures cover the layer bytes only. Re-tagging a signed artifact or serving an older signed version under a tag is not detected; pin digests when that matters.
Index ¶
- Constants
- Variables
- func IsProtected(annotations map[string]string) bool
- type Key
- func (k *Key) CanDecrypt() bool
- func (k *Key) CanEncrypt() bool
- func (k *Key) CanSign() bool
- func (k *Key) CanVerify() bool
- func (k *Key) Decrypt(blob []byte) ([]byte, error)
- func (k *Key) Describe() string
- func (k *Key) Encrypt(data []byte) ([]byte, error)
- func (k *Key) EncryptAlgorithm() string
- func (k *Key) Fingerprint() string
- func (k *Key) Identity() string
- func (k *Key) Private() bool
- func (k *Key) Protect(annotations map[string]string, data []byte, mode Mode) error
- func (k *Key) Recover(annotations map[string]string) ([]byte, error)
- func (k *Key) Sign(data []byte) ([]byte, error)
- func (k *Key) SignAlgorithm() string
- func (k *Key) Supports(mode Mode) error
- func (k *Key) Symmetric() bool
- func (k *Key) Verify(data, sig []byte) error
- func (k *Key) VerifyAnnotations(annotations map[string]string, data []byte) (Verification, error)
- type Mode
- type Verification
Constants ¶
const ( // AnnotationSignature holds the base64 signature (or MAC) of the YAML layer. AnnotationSignature = "io.docker.agent.signature" // AnnotationSignatureAlgorithm names the algorithm behind AnnotationSignature. AnnotationSignatureAlgorithm = "io.docker.agent.signature.algorithm" // AnnotationEncrypted holds a base64 authenticated-encrypted copy of the // whole YAML layer. AnnotationEncrypted = "io.docker.agent.encrypted" // AnnotationEncryptedAlgorithm names the algorithm behind AnnotationEncrypted. AnnotationEncryptedAlgorithm = "io.docker.agent.encrypted.algorithm" )
const ( AlgAESGCM = "aes-256-gcm" AlgRSAOAEP = "rsa-oaep-sha256-aes-256-gcm" )
const ( AlgHMACSHA256 = "hmac-sha256" AlgEd25519 = "ed25519" AlgECDSASHA256 = "ecdsa-sha256" AlgRSAPSSSHA256 = "rsa-pss-sha256" )
const FilePrefix = "file://"
FilePrefix marks a ResolveKey value as a path to a key file. It is a literal prefix, not a URL scheme: no percent-decoding or authority parsing.
const MinRSABits = 2048
MinRSABits is the smallest accepted RSA modulus; smaller keys are considered broken.
const MinSecretLen = 16
MinSecretLen is the minimum accepted length of a symmetric secret. The clear YAML plus its MAC/ciphertext is an offline oracle for guessing the secret, and HKDF adds no entropy, so short secrets are refused outright.
Variables ¶
var ( ErrNotProtected = errors.New("artifact is neither signed nor encrypted") ErrNotEncrypted = errors.New("artifact has no encrypted copy") ErrNotSigned = errors.New("artifact is not signed: an encrypted copy alone does not prove who published it when the key is asymmetric") ErrTampered = errors.New("encrypted copy does not match the artifact content") ErrAlgorithmMism = errors.New("algorithm mismatch") ErrEncryptNeedsPriv = errors.New("encrypt mode with an asymmetric key requires the private key, so the artifact can also be signed") )
var ( ErrCannotEncrypt = errors.New("key cannot encrypt") ErrCannotDecrypt = errors.New("key cannot decrypt: a private key or secret is required") ErrDecryption = errors.New("decryption failed") )
var ( ErrInvalidSignature = errors.New("signature verification failed") ErrCannotSign = errors.New("key cannot sign: a private key or secret is required") ErrCannotVerify = errors.New("key cannot verify signatures") )
var ErrSecretTooShort = fmt.Errorf("symmetric secret must be at least %d bytes (generate one with `openssl rand -hex 32`)", MinSecretLen)
Functions ¶
func IsProtected ¶
IsProtected reports whether annotations carry a signature or encrypted copy.
Types ¶
type Key ¶
type Key struct {
// contains filtered or unexported fields
}
Key is a symmetric secret or an asymmetric key (private, or public-only).
func ParseKey ¶
ParseKey detects the key kind from its encoding. PEM blocks and OpenSSH authorized_keys lines (with or without options) are parsed as asymmetric keys. Anything else is a raw symmetric secret (surrounding whitespace is trimmed so a trailing newline does not change the key) — unless it contains a PEM boundary or an OpenSSH key-type token, in which case it is treated as a broken key file and rejected. Failing closed here matters: a truncated or BOM-prefixed public key must never silently become an HMAC key made of public material. Random secrets never contain those markers.
func ResolveKey ¶ added in v1.133.0
ResolveKey parses a key given on the command line: a value prefixed with FilePrefix names a key file (a leading ~ is expanded, since shells do not expand it mid-word), anything else is the key material itself.
func (*Key) CanDecrypt ¶
CanDecrypt reports whether the key can decrypt (private material required).
func (*Key) CanEncrypt ¶
CanEncrypt reports whether the key can encrypt (any key of an encryption-capable type; a public key is enough).
func (*Key) Encrypt ¶
Encrypt returns an authenticated ciphertext of data that only holders of the secret (symmetric) or of the private key (asymmetric) can open. The algorithm label is bound as AEAD additional data.
Blob layouts:
- aes-256-gcm: nonce || ciphertext
- ecies-*: ephemeralPub || nonce || ciphertext
- rsa-oaep-*: wrappedKey || nonce || ciphertext
func (*Key) EncryptAlgorithm ¶
EncryptAlgorithm returns the encryption algorithm this key supports, or "" if the key type cannot encrypt (Ed25519).
func (*Key) Fingerprint ¶
Fingerprint returns a stable hex identifier for the key material: SHA-256 of the secret, or of the PKIX-encoded public key. Private and public halves of the same pair share a fingerprint.
func (*Key) Identity ¶
Identity extends Fingerprint with the key's role, so that the private and public halves of a pair — which have different verification capabilities — are told apart. Suitable as a cache key for verification results.
func (*Key) Private ¶
Private reports whether the key holds private material (a secret or a private key), as opposed to a public-only key.
func (*Key) Recover ¶
Recover decrypts the encrypted copy carried in annotations, returning the clear YAML. It works from the annotations alone, without the layer. Note that it does not check the signature; use VerifyAnnotations for that.
func (*Key) Sign ¶
Sign returns the raw signature (or MAC) of data. The signed message is domain-separated (see domainInput) so a signature cannot be reused in another protocol or under another algorithm label.
func (*Key) SignAlgorithm ¶
SignAlgorithm returns the signature algorithm this key supports.
func (*Key) Supports ¶
Supports reports whether the key can publish in mode, with a descriptive error when it cannot.
func (*Key) VerifyAnnotations ¶
VerifyAnnotations checks that data is what a holder of this key published, using the protection annotations carry, and reports what was checked.
A signature, when present, is always verified. An encrypted copy is decrypted and compared to data when the key can decrypt; a public key only checks its algorithm label and relies on the signature. With an asymmetric key a signature is mandatory, since anyone holding the public key could have produced the encrypted copy. ErrNotProtected is returned when the artifact carries no protection at all.
type Mode ¶
type Mode string
Mode selects what the publisher records in the annotations.
const ( // ModeSign records a signature (asymmetric key) or MAC (secret). // Holders of the matching public key or secret can verify integrity. ModeSign Mode = "sign" // ModeEncrypt records an encrypted copy of the whole YAML. Holders of the // secret or private key can both verify integrity and recover the YAML // from the annotation alone. With an asymmetric key a signature is // recorded as well (see the package security model). ModeEncrypt Mode = "encrypt" )
type Verification ¶
type Verification struct {
// SignatureAlgorithm is set when a signature was verified.
SignatureAlgorithm string
// EncryptedAlgorithm is set when the encrypted copy was decrypted and
// matched the content. It stays empty for a public key, which can only
// check the copy's algorithm label.
EncryptedAlgorithm string
}
Verification reports which protections VerifyAnnotations actually checked.
func (Verification) String ¶
func (v Verification) String() string