in yubikey/pkg/value/encryption/envelope/piv/service.go [111:153]
func (s *service) Encrypt(ctx context.Context, cleartext []byte) ([]byte, error) {
// Extract public keys
pivPublicKey := s.card.Public()
pivCompressed := elliptic.MarshalCompressed(pivPublicKey.Curve, pivPublicKey.X, pivPublicKey.Y)
// Identity tag
tag := sha256.Sum256(pivCompressed)
// Generate ephemeral key
eph, err := ecdsa.GenerateKey(pivPublicKey.Curve, rand.Reader)
if err != nil {
return nil, fmt.Errorf("unable to generate ephemeral key pair: %w", err)
}
ephCompressed := elliptic.MarshalCompressed(eph.Curve, eph.PublicKey.X, eph.PublicKey.Y)
// ECDH shared secret between ephemeral key and yubikey
sharedSecretNum, _ := eph.PublicKey.ScalarMult(s.card.Public().X, s.card.Public().Y, eph.D.Bytes())
sharedSecret := sharedSecretNum.Bytes()
// Derive AEAD cipher from parameters
aead, err := s.deriveAEAD(ephCompressed, pivCompressed, sharedSecret, chacha20poly1305.KeySize)
if err != nil {
return nil, fmt.Errorf("unable to initialize aead: %w", err)
}
// Encrypt
// Use fixed nonce to save space and also sharedsecret is derived from ephemeral
// key that act as nonce.
nonce := make([]byte, chacha20poly1305.NonceSize)
// Return encrypted content
body, err := cbor.Marshal(response{
EphCompressedPublic: ephCompressed,
Tag: tag[:4],
Payload: aead.Seal(nil, nonce, cleartext, nil),
})
if err != nil {
return nil, fmt.Errorf("unable to encode envelope: %w", err)
}
// No error
return body, nil
}