Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ the [versioning policy](https://teqpace-services.github.io/isopace/versioning/).

## [Unreleased]

### Added

- **`vault` capability interfaces** for HSM adapters. The `vault.Vault` façade is
now composed from `PINEncryptor`, `PINTranslator`, and `Macer`, so a hardware
adapter can implement exactly the operations its device supports — a
general-purpose PKCS#11 HSM provides `Macer` (and possibly `PINEncryptor`),
while a payment HSM additionally provides `PINTranslator`. `Vault` keeps the
same method set, so this is **source-compatible**. `PINTranslator` documents
the PCI PIN Security contract: a conforming hardware implementation must
re-encipher atomically inside the device so the clear PIN never leaves it, and
an adapter that cannot do so (e.g. stock PKCS#11) must not implement it.

### Changed

- **Docs:** corrected the `CoralPay` / `Zone` profile descriptions to state their
Expand Down
58 changes: 58 additions & 0 deletions vault/capabilities_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// Copyright (C) 2026 Teqpace Services Ltd.
//
// This file is part of Isopace, a financial transaction framework.
//
// Isopace is dual-licensed:
// - under the GNU Affero General Public License v3.0 or later (see LICENSE); or
// - under a commercial license from Teqpace Services Ltd. (see COMMERCIAL-LICENSE.md).
//
// Authorship is recorded in the AUTHORS file.

package vault_test

import (
"testing"

"github.com/teqpace-services/isopace/vault"
)

// The software vault satisfies every capability interface and the full Vault,
// verified from an external package (as an adapter would see them).
var (
_ vault.PINEncryptor = (*vault.SoftVault)(nil)
_ vault.PINTranslator = (*vault.SoftVault)(nil)
_ vault.Macer = (*vault.SoftVault)(nil)
_ vault.Vault = (*vault.SoftVault)(nil)
)

// macOnly stands in for a general-purpose (e.g. PKCS#11) HSM adapter that can
// MAC but cannot translate PINs.
type macOnly struct{}

func (macOnly) GenerateMAC(string, vault.MACAlgorithm, vault.Padding, []byte) ([]byte, error) {
return nil, nil
}

func (macOnly) VerifyMAC(string, vault.MACAlgorithm, vault.Padding, []byte, []byte) (bool, error) {
return false, nil
}

func TestCapabilityDetection(t *testing.T) {
// A full software vault advertises every capability.
var v vault.Vault = vault.NewSoftVault()
if _, ok := v.(vault.PINTranslator); !ok {
t.Error("SoftVault should satisfy PINTranslator")
}
if _, ok := v.(vault.Macer); !ok {
t.Error("SoftVault should satisfy Macer")
}

// A MAC-only adapter must NOT be mistakable for a PIN translator — this is
// exactly the check a switch performs before trusting a vault with PINs.
var m vault.Macer = macOnly{}
if _, ok := m.(vault.PINTranslator); ok {
t.Error("a Macer-only vault must not satisfy PINTranslator")
}
}
63 changes: 54 additions & 9 deletions vault/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,70 @@ import (
// ErrUnknownKey is returned when an operation names a key the vault does not hold.
var ErrUnknownKey = errors.New("vault: unknown key")

// Vault is the key-management façade: operations name keys by reference rather
// than passing key material, so the same calls work whether the keys live in
// software ([SoftVault]) or in an HSM behind an adapter. A real HSM (e.g. via
// PKCS#11) is a drop-in Vault implementation kept in a separate module so the
// core stays stdlib-only.
type Vault interface {
// The key-management façade is composed from small capability interfaces so a
// hardware adapter can implement exactly the operations its device supports.
// Operations name keys by reference rather than passing key material, so the
// same calls work whether the keys live in software ([SoftVault]) or in an HSM
// behind an adapter (kept in a separate module so the core stays stdlib-only).
//
// A general-purpose PKCS#11 HSM typically provides [Macer] (and possibly
// [PINEncryptor]); a payment HSM additionally provides [PINTranslator]. Callers
// should depend on the narrowest capability they need and type-assert for it:
//
// tr, ok := v.(vault.PINTranslator)
// if !ok { return errors.New("configured vault cannot translate PINs") }

// PINEncryptor enciphers a CLEAR PIN into a PIN block under a device-resident
// key. Because it takes the clear PIN, it is an issuer-side / trusted-context
// operation (e.g. PIN issuance) — at an acquiring switch the clear PIN must
// never be present, so a switch uses [PINTranslator] instead.
type PINEncryptor interface {
// EncryptPINBlock encodes pin for the format and encrypts the 8-byte block
// under the named PIN key (3DES ECB).
EncryptPINBlock(keyRef string, format PINBlockFormat, pin, pan string) ([]byte, error)
// TranslatePIN decrypts an encrypted PIN block under srcRef, re-encodes it in
// dstFormat, and re-encrypts under dstRef — the classic switch PIN-translate.
}

// PINTranslator re-enciphers an ENCRYPTED PIN block from one key (and format) to
// another — the classic acquirer/switch PIN-translate. The clear PIN does not
// appear in this interface.
//
// Contract: a conforming hardware implementation MUST perform the translation
// atomically inside the device so the clear PIN never leaves it (PCI PIN
// Security). An adapter that cannot translate without exposing the clear PIN in
// host memory (for example one limited to stock PKCS#11, which has no atomic
// translate mechanism) MUST NOT implement this interface — callers rely on its
// presence to mean the operation is PIN-secure.
type PINTranslator interface {
// TranslatePIN re-enciphers encBlock from srcRef/srcFormat to dstRef/dstFormat.
TranslatePIN(srcRef, dstRef string, encBlock []byte, pan string, srcFormat, dstFormat PINBlockFormat) ([]byte, error)
}

// Macer generates and verifies message authentication codes under a
// device-resident key; the key material never leaves the device.
type Macer interface {
// GenerateMAC computes a MAC over data under the named key.
GenerateMAC(keyRef string, alg MACAlgorithm, pad Padding, data []byte) ([]byte, error)
// VerifyMAC verifies a MAC over data under the named key.
VerifyMAC(keyRef string, alg MACAlgorithm, pad Padding, data, mac []byte) (bool, error)
}

var _ Vault = (*SoftVault)(nil)
// Vault is the full key-management façade: the composition of every capability,
// implemented by the software backend ([SoftVault], [SealedVault]) and by
// full-function payment HSM adapters. An adapter that supports only some
// capabilities should expose those interface types ([Macer], etc.) rather than
// the full Vault.
type Vault interface {
PINEncryptor
PINTranslator
Macer
}

var (
_ Vault = (*SoftVault)(nil)
_ PINEncryptor = (*SoftVault)(nil)
_ PINTranslator = (*SoftVault)(nil)
_ Macer = (*SoftVault)(nil)
)

// SoftVault is the in-process Vault: keys are held in memory and operations run
// with the Go standard library. It is for development, testing, and conformance
Expand Down
Loading