in yubikey/pkg/value/encryption/envelope/piv/service.go [56:109]
func (s *service) Decrypt(ctx context.Context, encrypted []byte) ([]byte, error) {
// Extract envelope
var r response
if err := cbor.Unmarshal(encrypted, &r); err != nil {
return nil, fmt.Errorf("unable to decode envelope: %w", err)
}
// Extract public keys
pivPublicKey := s.card.Public()
pivCompressed := elliptic.MarshalCompressed(pivPublicKey.Curve, pivPublicKey.X, pivPublicKey.Y)
// Identity tag
tag := sha256.Sum256(pivCompressed)
// Compare tag
if !security.SecureCompare(tag[:4], r.Tag) {
return nil, errors.New("invalid identity tag")
}
// Extract ephemeral public key
x, y := elliptic.UnmarshalCompressed(pivPublicKey.Curve, r.EphCompressedPublic)
if x == nil {
return nil, errors.New("cannot unmarshal ephemeral public key")
}
ephPub := &ecdsa.PublicKey{
Curve: pivPublicKey.Curve,
X: x,
Y: y,
}
// Compute shared secret
sharedSecret, err := s.card.SharedKey(ephPub, s.prompt)
if err != nil {
return nil, fmt.Errorf("unable to compute shared secret: %w", err)
}
// Derive AEAD cipher from parameters
aead, err := s.deriveAEAD(r.EphCompressedPublic, pivCompressed, sharedSecret, chacha20poly1305.KeySize)
if err != nil {
return nil, fmt.Errorf("unable to initialize aead: %w", err)
}
// Decrypt
// Use fixed nonce to save space and also sharedsecret is derived from ephemeral
// key that act as nonce.
nonce := make([]byte, chacha20poly1305.NonceSize)
clearText, err := aead.Open(nil, nonce, r.Payload, nil)
if err != nil {
return nil, fmt.Errorf("unable to decrypt payload: %w", err)
}
// No error
return clearText, nil
}