Documentation
¶
Overview ¶
Package yubihsm provides to a YubiHSM2 via idiomatic Go crypto APIs
The YubiHSM2 is the big-sibling to the YubiKey PIV dongle. See [What is YubiHSM 2] for Yubico's documentation on their HSM.
Connecting to a YubiHSM2 ¶
This package does not directly provide access to a YubiHSM2. Instead, you must run a separate _connector_ to provide access via USB. The most common option is Yubico's yubihsm-connector. This can either installed via source, your distribution's packages, or via Yubico's yubihsm2-sdk.
Alternatives to the yubihsm-connector include the yubihsm.rs example HTTP connector or its mockhsm.
Each connector provides a simple HTTP POST interface to the YubiHSM2, providing binary a command-response interface. The connector listens at localhost:12345 by default, but it is possible to connect to a remote instance of the connector:
conn := NewHTTPConnector(WithConnectorURL("http://1.2.3.4:5678/connector/api"))
YubiHSM2 Sessions ¶
All meaningful commands on a YubiHSM2 are sent within the context of a YubiHSM2 session. Each session is encrypted and authenticated via a symmetric authentication key.
An out-of-box YubiHSM2 is configured with a default authentication key derived from the password "password". You _must_ replace the default password and set a random key prior to using the HSM!
Up to 16 sessions may be active concurrently on the HSM. Each session has a 30-second inactivity timeout before the session expires. This package does not currently support keepalives; long-running processes should implement this via the Session.Ping method:
var session Session timer := time.NewTimer(20*time.Second) for _ := range timer.C { _, err := session.Echo(ctx, conn, 0xff) if err != nil { return err } }
YubiHSM2 Keys ¶
Keys can be loaded from a Session. This package currently does not support generating the keys, you can use an external tool such as yubihsm-shell instead.
The returned key object generally conforms the standard crypto key APIs, and can be used wherever a crypto.Signer or crypto.Deriver is used.
Supported key algorithms ¶
Only asymmetric key pairs (RSA, ECDSA, Ed25519) are supported. Any of these may be used to generate a signature. Only an RSA key can be used to decrypt a message.
Ed25519ph is not supported by the YubiHSM2, only plain Ed25519 works.
ECDH is not supported. (The crypto/ecdh API is closed from external extension; there is no way to implement a crypto/ecdh.PrivateKey in a third-party module.)
Index ¶
- Constants
- type AuthenticationOption
- type Connector
- type DeviceInfo
- type Error
- type HTTPConnector
- type HTTPOption
- type KeyPair
- func (k *KeyPair) AsCryptoDecrypter(ctx context.Context, conn Connector, session *Session) crypto.Decrypter
- func (k *KeyPair) AsCryptoSigner(ctx context.Context, conn Connector, session *Session) crypto.Signer
- func (k *KeyPair) Decrypt(ctx context.Context, conn Connector, session *Session, ciphertext []byte, ...) ([]byte, error)
- func (k *KeyPair) Equal(x crypto.PrivateKey) bool
- func (k *KeyPair) Public() crypto.PublicKey
- func (k *KeyPair) Sign(ctx context.Context, conn Connector, session *Session, digest []byte, ...) ([]byte, error)
- type ObjectID
- type Session
- func (s *Session) Authenticate(ctx context.Context, conn Connector, options ...AuthenticationOption) error
- func (s *Session) Close(ctx context.Context, conn Connector) error
- func (s *Session) GetDeviceInfo(ctx context.Context, conn Connector) (DeviceInfo, error)
- func (s *Session) LoadKeyPair(ctx context.Context, conn Connector, label string) (*KeyPair, error)
- func (s *Session) Ping(ctx context.Context, conn Connector, data ...byte) error
- type SessionKey
Constants ¶
const ( // ErrNotAuthenticated is returned if a command is sent over an // unauthenticated [Session]. ErrNotAuthenticated sessionError = "cannot send message over unauthenticated session" // ErrReauthenticationRequired is returned when the maximum number // of commands have been sent over an encrypted [Session]. The // session must be reauthenticated by calling [Session.Authenticate]. ErrReauthenticationRequired sessionError = "maximum messages sent; session must reauthenticate" // ErrIncorrectMAC is returned when a response from the YubiHSM2 // has an inccorect MAC. ErrIncorrectMAC sessionError = "session message MAC failed" // ErrInvalidMessage is returned when a response message cannot // be processed; generally indicating the length is incorrect. ErrInvalidMessage sessionError = "invalid response message" )
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AuthenticationOption ¶
AuthenticationOption configures an HSM Session.
func WithAuthenticationKeyID ¶
func WithAuthenticationKeyID(keyID ObjectID) AuthenticationOption
WithAuthenticationKeyID sets the authentication key ID of a session. If left unspecified the default HSM ID 1 is used.
func WithAuthenticationKeys ¶
func WithAuthenticationKeys(encryptionKey, macKey SessionKey) AuthenticationOption
WithAuthenticationKeys sets the authentication key of a session. If left unspecified the session uses keys derived from the default HSM password.
At most one of WithPassword or WithAuthenticationKeys may be used.
func WithPassword ¶
func WithPassword(password string) AuthenticationOption
WithPassword sets the authentication password of a session. If left unspecified the session uses the default HSM password.
At most one of WithPassword or WithAuthenticationKeys may be used.
type Connector ¶
Connector allows sending commands to a YubiHSM2.
[command] is a fully serialized HSM command. The YubiHSM2 has a maximum command length of 2028 bytes per the documentation of the Put Opaque command; therefore a connector must support sending a message this long.
type DeviceInfo ¶
type DeviceInfo struct { // Version string. Received from the HSM. Version string // Serial number. Received from the HSM. Serial uint32 // LogStore size expressed in number of log entries. Received from // the HSM. LogStore uint8 // LogLines used. Received from the HSM. LogLines uint8 // Algorithms supported by the device. Received from the HSM. Algorithms uint64 // Trusted is set to [true] if and only if the information was // received via an authenticated and encrypted [Session]. Trusted bool }
DeviceInfo contains information about the HSM.
https://developers.yubico.com/YubiHSM2/Commands/Device_Info.html
type Error ¶
type Error = yubihsm.LogicError
Error is the error type for a protocol error arising from an invalid response from a YubiHSM2.
type HTTPConnector ¶
type HTTPConnector struct {
// contains filtered or unexported fields
}
HTTPConnector is a Connector which provides access to a YubiHSM2 through the yubihsm-connector HTTP interface.
The zero value HTTPConnector is valid to use and connects to the yubihsm-connector at http://localhost:12345/connector/api using net/http.DefaultClient. This behavior can be customized with NewHTTPConnector.
func NewHTTPConnector ¶
func NewHTTPConnector(options ...HTTPOption) HTTPConnector
NewHTTPConnector creates an HTTPConnector using the provided configuration options.
func (*HTTPConnector) SendCommand ¶
SendCommand transmits the command and returns the YubiHSM2's response.
type HTTPOption ¶
type HTTPOption func(*httpConnector)
HTTPOption configures the behavior of the HTTPConnector created by NewHTTPConnector.
func WithConnectorURL ¶
func WithConnectorURL(url string) HTTPOption
WithConnectorURL configures the HTTPConnector to issue HTTP requests to the yubihsm-connector at the provided URL.
If not specified this defaults to "http://localhost:12345/connector/api".
NewHTTPConnector(WithConnectorURL("http://1.2.3.4:5678/connector/api"))
func WithHTTPClient ¶
func WithHTTPClient(client *http.Client) HTTPOption
WithHTTPClient configures the HTTPConnector to make HTTP requests using the provided HTTP client.
If not specified this defaults to http.DefaultClient.
type KeyPair ¶
type KeyPair struct {
// contains filtered or unexported fields
}
KeyPair manages either an RSA, ECDSA, or Ed25519 key on a YubiHSM2.
A KeyPair is a crypto.PrivateKey, but it does not directly implement either crypto.Signer or crypto.Decrypter. This is because invoking a command on an HSM requires a context.Context parameter, which is not supported by the crypto API.
Instead, use the KeyPair.Sign function directly, or used the KeyPair.AsCryptoSigner to wrap a (KeyPair, context) pair into a signing key object. The equivalent KeyPair.Decrypt and KeyPair.AsCryptoDecrypter are used to obtain a decryption key.
func (*KeyPair) AsCryptoDecrypter ¶
func (k *KeyPair) AsCryptoDecrypter(ctx context.Context, conn Connector, session *Session) crypto.Decrypter
AsCryptoDecrypter wraps the keypair into a type which can be used with the Go standard library's crypto.Decrypter. The returned key also defines an Equal() method to implement crypto.PrivateKey.
It does this by embedding the provided [ctx], [conn], and [session] into the returned object. This is non-idiomatic; particularly wrapping a context.Context into a returned structure contradicts standard practice.
However, this is the only approach which matches the API of crypto.Decrypter. Use of KeyPair.Sign should be preferred whenever compatibility with the standard library isn't needed.
This does not confirm that the HSM key is compatible with signing, nor whether the Effective Capabilities are sufficient.
func (*KeyPair) AsCryptoSigner ¶
func (k *KeyPair) AsCryptoSigner(ctx context.Context, conn Connector, session *Session) crypto.Signer
AsCryptoSigner wraps the keypair into a type which can be used with the Go standard library's crypto.Signer. The returned key also defines an Equal() method to implement crypto.PrivateKey.
It does this by embedding the provided [ctx], [conn], and [session] into the returned object. This is non-idiomatic; particularly wrapping a context.Context into a returned structure contradicts standard practice.
However, this is the only approach which matches the API of crypto.Signer. Use of KeyPair.Sign should be preferred whenever compatibility with the standard library isn't needed.
This does not confirm that the HSM key is compatible with signing, nor whether the Effective Capabilities are sufficient.
func (*KeyPair) Decrypt ¶
func (k *KeyPair) Decrypt(ctx context.Context, conn Connector, session *Session, ciphertext []byte, opts crypto.DecrypterOpts) ([]byte, error)
Decrypt the [message] in the YubiHSM and return the plaintext.
This mimics the semantics of crypto.Signer.Sign, in particular the value of [opt]. See the details of rsa.PrivateKey.Decrypt.
This function will fail if the HSM key type is incompatible with decryption or if the Effective Capabilities are insufficient.
func (*KeyPair) Equal ¶
func (k *KeyPair) Equal(x crypto.PrivateKey) bool
Equal checks if two private keys are equal. It implements crypto.PrivateKey.
This checks for logical equivalency of the keys; not if they are the exact same objects on the same YubiHSM2. As an example, a private key imported to multiple HSMs would compare equal, even if object IDs did not match.
func (*KeyPair) Public ¶
Public returns the public key. It implements crypto.PrivateKey.
It will be either an rsa.PublicKey, ecdsa.PublicKey, or ed25519.PublicKey depending upon the type of the key in the YubiHSM.
func (*KeyPair) Sign ¶
func (k *KeyPair) Sign(ctx context.Context, conn Connector, session *Session, digest []byte, opts crypto.SignerOpts) ([]byte, error)
Sign the message [digest] in the YubiHSM and return the signature.
This mimics the semantics of crypto.Signer.Sign, in particular the value of [opt]. See the details of rsa.PrivateKey.Sign and ecdsa.PrivateKey.Sign for additional details. Both PKCS1v1.5 and PSS signatures are supported with RSA keys. Only basic Ed25519 signatures are supported; the YubiHSM2 supports neither the Ed25519ph or Ed25519ctx variants.
This function will fail if the HSM key type is incompatible with decryption or if the Effective Capabilities are insufficient.
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
Session is an encrypted and authenticated communication channel to an HSM. It can be used to exchange commands and responses to the HSM.
The zero Session is valid to use.
var session Session err := session.Authenticate(ctx, conn)
func (*Session) Authenticate ¶
func (s *Session) Authenticate(ctx context.Context, conn Connector, options ...AuthenticationOption) error
Authenticate performs the cryptographic exchange to authenticate with the YubiHSM2 and establish an encrypted communication channel.
func (*Session) Close ¶
Close cleanly shuts the session down. A closed Session cannot be reused. After closing a session any [KeyPair]s loaded will no longer work.
This does not implement the standard io.Closer interface since a context.Context and Connector must be provided to send a close message to the HSM.
func (*Session) GetDeviceInfo ¶
GetDeviceInfo retrieves the HSM's status information.
This is the only command other than Session.Authenticate which can be called on an unauthenticated session, and the only command which can be called on either an authenticated _or_ unauthenticated session.
If the session isn't authenticated then the returned device information itself is neither encrypted not authenticated. It therefore should not be trusted; but this can be useful sometimes to e.g. lookup an HSM's configuration by its serial number prior to establishing a session.
If untrusted device information is used then it should be confirmed after authenticating the session by requesting the device info again and confirming against trusted values:
var session Session untrustedDevInfo, _ := session.GetDeviceInfo(ctx, conn) authKey, _ := keys[untrustedDevInfo.Serial] _ = session.Authenticate(ctx, conn, WithAuthenticationKeys(authKey)) trustedDevInfo, _ := session.GetDeviceInfo(ctx, conn) if trustedDevInfo.Serial != untrustedDevInfo.Serial { println("Lies!") }
func (*Session) LoadKeyPair ¶
LoadKeyPair looks up the asymmetric keypair in the HSM using the provided [label] and returns a KeyPair which can be used to sign messages or decrypt ciphertext.
The returned key's public will be one of an *ecdsa.PublicKey, ed25519.PublicKey, or an *rsa.PublicKey. Dependent upon the key's type and the Effective Capabilities KeyPair.Sign and/or KeyPair.Decrypt will work.
func (*Session) Ping ¶
Ping sends a [ping] message to the YubiHSM2 and returns the received [pong] response. It uses the Echo command to send and receive data.
The most common use of the echo command is to implement a session keepalive heartbeat; to mimic the yubihsm-shell's behavior use:
err = session.Ping(ctx, conn, 0xff)
type SessionKey ¶
type SessionKey [sessionKeyLen]byte
SessionKey is a random key used to authenticate and encrypt a YubiHSM2 session.
These should always be randomly generated.