Initial QSfera import
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Code coverage profiles and other test artifacts
|
||||
*.out
|
||||
coverage.*
|
||||
*.coverprofile
|
||||
profile.cov
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
# env file
|
||||
.env
|
||||
|
||||
# Editor/IDE
|
||||
# .idea/
|
||||
# .vscode/
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
Changes
|
||||
=======
|
||||
|
||||
v1.0.0 - 18 Aug 2025
|
||||
* Initial release
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 lestrrat-go
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
# github.com/lestrrat-go/dsig [](https://github.com/lestrrat-go/dsig/actions/workflows/ci.yml) [](https://pkg.go.dev/github.com/lestrrat-go/dsig) [](https://codecov.io/github/lestrrat-go/dsig?branch=v1)
|
||||
|
||||
Go module providing low-level digital signature operations.
|
||||
|
||||
While there are many standards for generating and verifying digital signatures, the core operations are virtually the same. This module implements the core functionality of digital signature generation / verifications in a framework agnostic way.
|
||||
|
||||
# Features
|
||||
|
||||
* RSA signatures (PKCS1v15 and PSS)
|
||||
* ECDSA signatures (P-256, P-384, P-521)
|
||||
* EdDSA signatures (Ed25519, Ed448)
|
||||
* HMAC signatures (SHA-256, SHA-384, SHA-512)
|
||||
* Support for crypto.Signer interface
|
||||
* Allows for dynamic additions of algorithms in limited cases.
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
<!-- INCLUDE(examples/dsig_readme_example_test.go) -->
|
||||
```go
|
||||
package examples_test
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
|
||||
"github.com/lestrrat-go/dsig"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
payload := []byte("hello world")
|
||||
|
||||
// RSA signing and verification
|
||||
{
|
||||
privKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to generate RSA key: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Sign with RSA-PSS SHA256
|
||||
signature, err := dsig.Sign(privKey, dsig.RSAPSSWithSHA256, payload, nil)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to sign with RSA: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify with RSA-PSS SHA256
|
||||
err = dsig.Verify(&privKey.PublicKey, dsig.RSAPSSWithSHA256, payload, signature)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to verify RSA signature: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ECDSA signing and verification
|
||||
{
|
||||
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to generate ECDSA key: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Sign with ECDSA P-256 SHA256
|
||||
signature, err := dsig.Sign(privKey, dsig.ECDSAWithP256AndSHA256, payload, nil)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to sign with ECDSA: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify with ECDSA P-256 SHA256
|
||||
err = dsig.Verify(&privKey.PublicKey, dsig.ECDSAWithP256AndSHA256, payload, signature)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to verify ECDSA signature: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// EdDSA signing and verification
|
||||
{
|
||||
pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to generate Ed25519 key: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Sign with EdDSA
|
||||
signature, err := dsig.Sign(privKey, dsig.EdDSA, payload, nil)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to sign with EdDSA: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify with EdDSA
|
||||
err = dsig.Verify(pubKey, dsig.EdDSA, payload, signature)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to verify EdDSA signature: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// HMAC signing and verification
|
||||
{
|
||||
key := []byte("secret-key")
|
||||
|
||||
// Sign with HMAC SHA256
|
||||
signature, err := dsig.Sign(key, dsig.HMACWithSHA256, payload, nil)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to sign with HMAC: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify with HMAC SHA256
|
||||
err = dsig.Verify(key, dsig.HMACWithSHA256, payload, signature)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to verify HMAC signature: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// OUTPUT:
|
||||
}
|
||||
```
|
||||
source: [examples/dsig_readme_example_test.go](https://github.com/lestrrat-go/dsig/blob/v1/examples/dsig_readme_example_test.go)
|
||||
<!-- END INCLUDE -->
|
||||
|
||||
# Supported Algorithms
|
||||
|
||||
| Constant | Algorithm | Key Type |
|
||||
|----------|-----------|----------|
|
||||
| `HMACWithSHA256` | HMAC using SHA-256 | []byte |
|
||||
| `HMACWithSHA384` | HMAC using SHA-384 | []byte |
|
||||
| `HMACWithSHA512` | HMAC using SHA-512 | []byte |
|
||||
| `RSAPKCS1v15WithSHA256` | RSA PKCS#1 v1.5 using SHA-256 | *rsa.PrivateKey / *rsa.PublicKey |
|
||||
| `RSAPKCS1v15WithSHA384` | RSA PKCS#1 v1.5 using SHA-384 | *rsa.PrivateKey / *rsa.PublicKey |
|
||||
| `RSAPKCS1v15WithSHA512` | RSA PKCS#1 v1.5 using SHA-512 | *rsa.PrivateKey / *rsa.PublicKey |
|
||||
| `RSAPSSWithSHA256` | RSA PSS using SHA-256 | *rsa.PrivateKey / *rsa.PublicKey |
|
||||
| `RSAPSSWithSHA384` | RSA PSS using SHA-384 | *rsa.PrivateKey / *rsa.PublicKey |
|
||||
| `RSAPSSWithSHA512` | RSA PSS using SHA-512 | *rsa.PrivateKey / *rsa.PublicKey |
|
||||
| `ECDSAWithP256AndSHA256` | ECDSA using P-256 and SHA-256 | *ecdsa.PrivateKey / *ecdsa.PublicKey |
|
||||
| `ECDSAWithP384AndSHA384` | ECDSA using P-384 and SHA-384 | *ecdsa.PrivateKey / *ecdsa.PublicKey |
|
||||
| `ECDSAWithP521AndSHA512` | ECDSA using P-521 and SHA-512 | *ecdsa.PrivateKey / *ecdsa.PublicKey |
|
||||
| `EdDSA` | EdDSA using Ed25519 or Ed448 | ed25519.PrivateKey / ed25519.PublicKey |
|
||||
|
||||
# Description
|
||||
|
||||
This library provides low-level digital signature operations. It does minimal parameter validation for performance, uses strongly typed APIs, and has minimal dependencies.
|
||||
|
||||
# Contributions
|
||||
|
||||
## Issues
|
||||
|
||||
For bug reports and feature requests, please include failing tests when possible.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
Please include tests that exercise your changes.
|
||||
|
||||
# Related Libraries
|
||||
|
||||
* [github.com/lestrrat-go/jwx](https://github.com/lestrrat-go/jwx) - JOSE (JWA/JWE/JWK/JWS/JWT) implementation
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package dsig
|
||||
|
||||
// This file defines verbose algorithm name constants that can be mapped to by
|
||||
// different standards (RFC7518, FIDO, etc.) for interoperability.
|
||||
//
|
||||
// The algorithm names are intentionally verbose to avoid any ambiguity about
|
||||
// the exact cryptographic operations being performed.
|
||||
|
||||
const (
|
||||
// HMAC signature algorithms
|
||||
// These use Hash-based Message Authentication Code with specified hash functions
|
||||
HMACWithSHA256 = "HMAC_WITH_SHA256"
|
||||
HMACWithSHA384 = "HMAC_WITH_SHA384"
|
||||
HMACWithSHA512 = "HMAC_WITH_SHA512"
|
||||
|
||||
// RSA signature algorithms with PKCS#1 v1.5 padding
|
||||
// These use RSA signatures with PKCS#1 v1.5 padding and specified hash functions
|
||||
RSAPKCS1v15WithSHA256 = "RSA_PKCS1v15_WITH_SHA256"
|
||||
RSAPKCS1v15WithSHA384 = "RSA_PKCS1v15_WITH_SHA384"
|
||||
RSAPKCS1v15WithSHA512 = "RSA_PKCS1v15_WITH_SHA512"
|
||||
|
||||
// RSA signature algorithms with PSS padding
|
||||
// These use RSA signatures with Probabilistic Signature Scheme (PSS) padding
|
||||
RSAPSSWithSHA256 = "RSA_PSS_WITH_SHA256"
|
||||
RSAPSSWithSHA384 = "RSA_PSS_WITH_SHA384"
|
||||
RSAPSSWithSHA512 = "RSA_PSS_WITH_SHA512"
|
||||
|
||||
// ECDSA signature algorithms
|
||||
// These use Elliptic Curve Digital Signature Algorithm with specified curves and hash functions
|
||||
ECDSAWithP256AndSHA256 = "ECDSA_WITH_P256_AND_SHA256"
|
||||
ECDSAWithP384AndSHA384 = "ECDSA_WITH_P384_AND_SHA384"
|
||||
ECDSAWithP521AndSHA512 = "ECDSA_WITH_P521_AND_SHA512"
|
||||
|
||||
// EdDSA signature algorithms
|
||||
// These use Edwards-curve Digital Signature Algorithm (supports Ed25519 and Ed448)
|
||||
EdDSA = "EDDSA"
|
||||
)
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// cryptosign is a low-level function that signs a payload using a crypto.Signer.
|
||||
// If hash is crypto.Hash(0), the payload is signed directly without hashing.
|
||||
// Otherwise, the payload is hashed using the specified hash function before signing.
|
||||
//
|
||||
// rr is an io.Reader that provides randomness for signing. If rr is nil, it defaults to rand.Reader.
|
||||
func cryptosign(signer crypto.Signer, payload []byte, hash crypto.Hash, opts crypto.SignerOpts, rr io.Reader) ([]byte, error) {
|
||||
if rr == nil {
|
||||
rr = rand.Reader
|
||||
}
|
||||
|
||||
var digest []byte
|
||||
if hash == crypto.Hash(0) {
|
||||
digest = payload
|
||||
} else {
|
||||
h := hash.New()
|
||||
if _, err := h.Write(payload); err != nil {
|
||||
return nil, fmt.Errorf(`failed to write payload to hash: %w`, err)
|
||||
}
|
||||
digest = h.Sum(nil)
|
||||
}
|
||||
return signer.Sign(rr, digest, opts)
|
||||
}
|
||||
|
||||
// SignCryptoSigner generates a signature using a crypto.Signer interface.
|
||||
// This function can be used for hardware security modules, smart cards,
|
||||
// and other implementations of the crypto.Signer interface.
|
||||
//
|
||||
// rr is an io.Reader that provides randomness for signing. If rr is nil, it defaults to rand.Reader.
|
||||
//
|
||||
// Returns the signature bytes or an error if signing fails.
|
||||
func SignCryptoSigner(signer crypto.Signer, raw []byte, h crypto.Hash, opts crypto.SignerOpts, rr io.Reader) ([]byte, error) {
|
||||
if signer == nil {
|
||||
return nil, fmt.Errorf("dsig.SignCryptoSigner: signer is nil")
|
||||
}
|
||||
return cryptosign(signer, raw, h, opts, rr)
|
||||
}
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
// Package dsig provides digital signature operations for Go.
|
||||
// It contains low-level signature generation and verification tools that
|
||||
// can be used by other signing libraries
|
||||
//
|
||||
// The package follows these design principles:
|
||||
// 1. Does minimal checking of input parameters (for performance); callers need to ensure that the parameters are valid.
|
||||
// 2. All exported functions are strongly typed (i.e. they do not take `any` types unless they absolutely have to).
|
||||
// 3. Does not rely on other high-level packages (standalone, except for internal packages).
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
"hash"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Family represents the cryptographic algorithm family
|
||||
type Family int
|
||||
|
||||
const (
|
||||
InvalidFamily Family = iota
|
||||
HMAC
|
||||
RSA
|
||||
ECDSA
|
||||
EdDSAFamily
|
||||
maxFamily
|
||||
)
|
||||
|
||||
// String returns the string representation of the Family
|
||||
func (f Family) String() string {
|
||||
switch f {
|
||||
case HMAC:
|
||||
return "HMAC"
|
||||
case RSA:
|
||||
return "RSA"
|
||||
case ECDSA:
|
||||
return "ECDSA"
|
||||
case EdDSAFamily:
|
||||
return "EdDSA"
|
||||
default:
|
||||
return "InvalidFamily"
|
||||
}
|
||||
}
|
||||
|
||||
// AlgorithmInfo contains metadata about a digital signature algorithm
|
||||
type AlgorithmInfo struct {
|
||||
Family Family // The cryptographic family (HMAC, RSA, ECDSA, EdDSA)
|
||||
Meta any // Family-specific metadata
|
||||
}
|
||||
|
||||
// HMACFamilyMeta contains metadata specific to HMAC algorithms
|
||||
type HMACFamilyMeta struct {
|
||||
HashFunc func() hash.Hash // Hash function constructor
|
||||
}
|
||||
|
||||
// RSAFamilyMeta contains metadata specific to RSA algorithms
|
||||
type RSAFamilyMeta struct {
|
||||
Hash crypto.Hash // Hash algorithm
|
||||
PSS bool // Whether to use PSS padding (false = PKCS#1 v1.5)
|
||||
}
|
||||
|
||||
// ECDSAFamilyMeta contains metadata specific to ECDSA algorithms
|
||||
type ECDSAFamilyMeta struct {
|
||||
Hash crypto.Hash // Hash algorithm
|
||||
}
|
||||
|
||||
// EdDSAFamilyMeta contains metadata specific to EdDSA algorithms
|
||||
// Currently EdDSA doesn't need specific metadata, but this provides extensibility
|
||||
type EdDSAFamilyMeta struct {
|
||||
// Reserved for future use
|
||||
}
|
||||
|
||||
var algorithms = make(map[string]AlgorithmInfo)
|
||||
var muAlgorithms sync.RWMutex
|
||||
|
||||
// RegisterAlgorithm registers a new digital signature algorithm with the specified family and metadata.
|
||||
//
|
||||
// info.Meta should contain extra metadata for some algorithms. Currently HMAC, RSA,
|
||||
// and ECDSA family of algorithms need their respective metadata (HMACFamilyMeta,
|
||||
// RSAFamilyMeta, and ECDSAFamilyMeta). Metadata for other families are ignored.
|
||||
func RegisterAlgorithm(name string, info AlgorithmInfo) error {
|
||||
muAlgorithms.Lock()
|
||||
defer muAlgorithms.Unlock()
|
||||
|
||||
// Validate the metadata matches the family
|
||||
switch info.Family {
|
||||
case HMAC:
|
||||
if _, ok := info.Meta.(HMACFamilyMeta); !ok {
|
||||
return fmt.Errorf("invalid HMAC metadata for algorithm %s", name)
|
||||
}
|
||||
case RSA:
|
||||
if _, ok := info.Meta.(RSAFamilyMeta); !ok {
|
||||
return fmt.Errorf("invalid RSA metadata for algorithm %s", name)
|
||||
}
|
||||
case ECDSA:
|
||||
if _, ok := info.Meta.(ECDSAFamilyMeta); !ok {
|
||||
return fmt.Errorf("invalid ECDSA metadata for algorithm %s", name)
|
||||
}
|
||||
case EdDSAFamily:
|
||||
// EdDSA metadata is optional for now
|
||||
default:
|
||||
return fmt.Errorf("unsupported algorithm family %s for algorithm %s", info.Family, name)
|
||||
}
|
||||
|
||||
algorithms[name] = info
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAlgorithmInfo retrieves the algorithm information for a given algorithm name.
|
||||
// Returns the info and true if found, zero value and false if not found.
|
||||
func GetAlgorithmInfo(name string) (AlgorithmInfo, bool) {
|
||||
muAlgorithms.RLock()
|
||||
defer muAlgorithms.RUnlock()
|
||||
|
||||
info, ok := algorithms[name]
|
||||
return info, ok
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Register all standard algorithms with their metadata
|
||||
toRegister := map[string]AlgorithmInfo{
|
||||
// HMAC algorithms
|
||||
HMACWithSHA256: {
|
||||
Family: HMAC,
|
||||
Meta: HMACFamilyMeta{
|
||||
HashFunc: sha256.New,
|
||||
},
|
||||
},
|
||||
HMACWithSHA384: {
|
||||
Family: HMAC,
|
||||
Meta: HMACFamilyMeta{
|
||||
HashFunc: sha512.New384,
|
||||
},
|
||||
},
|
||||
HMACWithSHA512: {
|
||||
Family: HMAC,
|
||||
Meta: HMACFamilyMeta{
|
||||
HashFunc: sha512.New,
|
||||
},
|
||||
},
|
||||
|
||||
// RSA PKCS#1 v1.5 algorithms
|
||||
RSAPKCS1v15WithSHA256: {
|
||||
Family: RSA,
|
||||
Meta: RSAFamilyMeta{
|
||||
Hash: crypto.SHA256,
|
||||
PSS: false,
|
||||
},
|
||||
},
|
||||
RSAPKCS1v15WithSHA384: {
|
||||
Family: RSA,
|
||||
Meta: RSAFamilyMeta{
|
||||
Hash: crypto.SHA384,
|
||||
PSS: false,
|
||||
},
|
||||
},
|
||||
RSAPKCS1v15WithSHA512: {
|
||||
Family: RSA,
|
||||
Meta: RSAFamilyMeta{
|
||||
Hash: crypto.SHA512,
|
||||
PSS: false,
|
||||
},
|
||||
},
|
||||
|
||||
// RSA PSS algorithms
|
||||
RSAPSSWithSHA256: {
|
||||
Family: RSA,
|
||||
Meta: RSAFamilyMeta{
|
||||
Hash: crypto.SHA256,
|
||||
PSS: true,
|
||||
},
|
||||
},
|
||||
RSAPSSWithSHA384: {
|
||||
Family: RSA,
|
||||
Meta: RSAFamilyMeta{
|
||||
Hash: crypto.SHA384,
|
||||
PSS: true,
|
||||
},
|
||||
},
|
||||
RSAPSSWithSHA512: {
|
||||
Family: RSA,
|
||||
Meta: RSAFamilyMeta{
|
||||
Hash: crypto.SHA512,
|
||||
PSS: true,
|
||||
},
|
||||
},
|
||||
|
||||
// ECDSA algorithms
|
||||
ECDSAWithP256AndSHA256: {
|
||||
Family: ECDSA,
|
||||
Meta: ECDSAFamilyMeta{
|
||||
Hash: crypto.SHA256,
|
||||
},
|
||||
},
|
||||
ECDSAWithP384AndSHA384: {
|
||||
Family: ECDSA,
|
||||
Meta: ECDSAFamilyMeta{
|
||||
Hash: crypto.SHA384,
|
||||
},
|
||||
},
|
||||
ECDSAWithP521AndSHA512: {
|
||||
Family: ECDSA,
|
||||
Meta: ECDSAFamilyMeta{
|
||||
Hash: crypto.SHA512,
|
||||
},
|
||||
},
|
||||
|
||||
// EdDSA algorithm
|
||||
EdDSA: {
|
||||
Family: EdDSAFamily,
|
||||
Meta: EdDSAFamilyMeta{},
|
||||
},
|
||||
}
|
||||
|
||||
for name, info := range toRegister {
|
||||
if err := RegisterAlgorithm(name, info); err != nil {
|
||||
panic(fmt.Sprintf("failed to register algorithm %s: %v", name, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"encoding/asn1"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/lestrrat-go/dsig/internal/ecutil"
|
||||
)
|
||||
|
||||
|
||||
func ecdsaGetSignerKey(key any) (*ecdsa.PrivateKey, crypto.Signer, bool, error) {
|
||||
cs, isCryptoSigner := key.(crypto.Signer)
|
||||
if isCryptoSigner {
|
||||
if !isValidECDSAKey(key) {
|
||||
return nil, nil, false, fmt.Errorf(`invalid key type %T for ECDSA algorithm`, key)
|
||||
}
|
||||
|
||||
switch key.(type) {
|
||||
case ecdsa.PrivateKey, *ecdsa.PrivateKey:
|
||||
// if it's ecdsa.PrivateKey, it's more efficient to
|
||||
// go through the non-crypto.Signer route. Set isCryptoSigner to false
|
||||
isCryptoSigner = false
|
||||
}
|
||||
}
|
||||
|
||||
if isCryptoSigner {
|
||||
return nil, cs, true, nil
|
||||
}
|
||||
|
||||
privkey, ok := key.(*ecdsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, nil, false, fmt.Errorf(`invalid key type %T. *ecdsa.PrivateKey is required`, key)
|
||||
}
|
||||
return privkey, nil, false, nil
|
||||
}
|
||||
|
||||
// UnpackASN1ECDSASignature unpacks an ASN.1 encoded ECDSA signature into r and s values.
|
||||
// This is typically used when working with crypto.Signer interfaces that return ASN.1 encoded signatures.
|
||||
func UnpackASN1ECDSASignature(signed []byte, r, s *big.Int) error {
|
||||
// Okay, this is silly, but hear me out. When we use the
|
||||
// crypto.Signer interface, the PrivateKey is hidden.
|
||||
// But we need some information about the key (its bit size).
|
||||
//
|
||||
// So while silly, we're going to have to make another call
|
||||
// here and fetch the Public key.
|
||||
// (This probably means that this information should be cached somewhere)
|
||||
var p struct {
|
||||
R *big.Int // TODO: get this from a pool?
|
||||
S *big.Int
|
||||
}
|
||||
if _, err := asn1.Unmarshal(signed, &p); err != nil {
|
||||
return fmt.Errorf(`failed to unmarshal ASN1 encoded signature: %w`, err)
|
||||
}
|
||||
|
||||
r.Set(p.R)
|
||||
s.Set(p.S)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnpackECDSASignature unpacks a JWS-format ECDSA signature into r and s values.
|
||||
// The signature should be in the format specified by RFC 7515 (r||s as fixed-length byte arrays).
|
||||
func UnpackECDSASignature(signature []byte, pubkey *ecdsa.PublicKey, r, s *big.Int) error {
|
||||
keySize := ecutil.CalculateKeySize(pubkey.Curve)
|
||||
if len(signature) != keySize*2 {
|
||||
return fmt.Errorf(`invalid signature length for curve %q`, pubkey.Curve.Params().Name)
|
||||
}
|
||||
|
||||
r.SetBytes(signature[:keySize])
|
||||
s.SetBytes(signature[keySize:])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PackECDSASignature packs the r and s values from an ECDSA signature into a JWS-format byte slice.
|
||||
// The output format follows RFC 7515: r||s as fixed-length byte arrays.
|
||||
func PackECDSASignature(r *big.Int, sbig *big.Int, curveBits int) ([]byte, error) {
|
||||
keyBytes := curveBits / 8
|
||||
if curveBits%8 > 0 {
|
||||
keyBytes++
|
||||
}
|
||||
|
||||
// Serialize r and s into fixed-length bytes
|
||||
rBytes := r.Bytes()
|
||||
rBytesPadded := make([]byte, keyBytes)
|
||||
copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)
|
||||
|
||||
sBytes := sbig.Bytes()
|
||||
sBytesPadded := make([]byte, keyBytes)
|
||||
copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)
|
||||
|
||||
// Output as r||s
|
||||
return append(rBytesPadded, sBytesPadded...), nil
|
||||
}
|
||||
|
||||
// SignECDSA generates an ECDSA signature for the given payload using the specified private key and hash.
|
||||
// The raw parameter should be the pre-computed signing input (typically header.payload).
|
||||
//
|
||||
// rr is an io.Reader that provides randomness for signing. if rr is nil, it defaults to rand.Reader.
|
||||
func SignECDSA(key *ecdsa.PrivateKey, payload []byte, h crypto.Hash, rr io.Reader) ([]byte, error) {
|
||||
if !isValidECDSAKey(key) {
|
||||
return nil, fmt.Errorf(`invalid key type %T for ECDSA algorithm`, key)
|
||||
}
|
||||
hh := h.New()
|
||||
if _, err := hh.Write(payload); err != nil {
|
||||
return nil, fmt.Errorf(`failed to write payload using ecdsa: %w`, err)
|
||||
}
|
||||
digest := hh.Sum(nil)
|
||||
|
||||
if rr == nil {
|
||||
rr = rand.Reader
|
||||
}
|
||||
|
||||
// Sign and get r, s values
|
||||
r, s, err := ecdsa.Sign(rr, key, digest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to sign payload using ecdsa: %w`, err)
|
||||
}
|
||||
|
||||
return PackECDSASignature(r, s, key.Curve.Params().BitSize)
|
||||
}
|
||||
|
||||
// SignECDSACryptoSigner generates an ECDSA signature using a crypto.Signer interface.
|
||||
// This function works with hardware security modules and other crypto.Signer implementations.
|
||||
// The signature is converted from ASN.1 format to JWS format (r||s).
|
||||
//
|
||||
// rr is an io.Reader that provides randomness for signing. If rr is nil, it defaults to rand.Reader.
|
||||
func SignECDSACryptoSigner(signer crypto.Signer, raw []byte, h crypto.Hash, rr io.Reader) ([]byte, error) {
|
||||
signed, err := SignCryptoSigner(signer, raw, h, h, rr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to sign payload using crypto.Signer: %w`, err)
|
||||
}
|
||||
|
||||
return signECDSACryptoSigner(signer, signed)
|
||||
}
|
||||
|
||||
func signECDSACryptoSigner(signer crypto.Signer, signed []byte) ([]byte, error) {
|
||||
cpub := signer.Public()
|
||||
pubkey, ok := cpub.(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`expected *ecdsa.PublicKey, got %T`, pubkey)
|
||||
}
|
||||
curveBits := pubkey.Curve.Params().BitSize
|
||||
|
||||
var r, s big.Int
|
||||
if err := UnpackASN1ECDSASignature(signed, &r, &s); err != nil {
|
||||
return nil, fmt.Errorf(`failed to unpack ASN1 encoded signature: %w`, err)
|
||||
}
|
||||
|
||||
return PackECDSASignature(&r, &s, curveBits)
|
||||
}
|
||||
|
||||
func ecdsaVerify(key *ecdsa.PublicKey, buf []byte, h crypto.Hash, r, s *big.Int) error {
|
||||
hasher := h.New()
|
||||
hasher.Write(buf)
|
||||
digest := hasher.Sum(nil)
|
||||
if !ecdsa.Verify(key, digest, r, s) {
|
||||
return NewVerificationError("invalid ECDSA signature")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyECDSA verifies an ECDSA signature for the given payload.
|
||||
// This function verifies the signature using the specified public key and hash algorithm.
|
||||
// The payload parameter should be the pre-computed signing input (typically header.payload).
|
||||
func VerifyECDSA(key *ecdsa.PublicKey, payload, signature []byte, h crypto.Hash) error {
|
||||
var r, s big.Int
|
||||
if err := UnpackECDSASignature(signature, key, &r, &s); err != nil {
|
||||
return fmt.Errorf("dsig.VerifyECDSA: failed to unpack ECDSA signature: %w", err)
|
||||
}
|
||||
|
||||
return ecdsaVerify(key, payload, h, &r, &s)
|
||||
}
|
||||
|
||||
// VerifyECDSACryptoSigner verifies an ECDSA signature for crypto.Signer implementations.
|
||||
// This function is useful for verifying signatures created by hardware security modules
|
||||
// or other implementations of the crypto.Signer interface.
|
||||
// The payload parameter should be the pre-computed signing input (typically header.payload).
|
||||
func VerifyECDSACryptoSigner(signer crypto.Signer, payload, signature []byte, h crypto.Hash) error {
|
||||
var pubkey *ecdsa.PublicKey
|
||||
switch cpub := signer.Public(); cpub := cpub.(type) {
|
||||
case ecdsa.PublicKey:
|
||||
pubkey = &cpub
|
||||
case *ecdsa.PublicKey:
|
||||
pubkey = cpub
|
||||
default:
|
||||
return fmt.Errorf(`dsig.VerifyECDSACryptoSigner: expected *ecdsa.PublicKey, got %T`, cpub)
|
||||
}
|
||||
|
||||
var r, s big.Int
|
||||
if err := UnpackECDSASignature(signature, pubkey, &r, &s); err != nil {
|
||||
return fmt.Errorf("dsig.VerifyECDSACryptoSigner: failed to unpack ASN.1 encoded ECDSA signature: %w", err)
|
||||
}
|
||||
|
||||
return ecdsaVerify(pubkey, payload, h, &r, &s)
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ed25519"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func eddsaGetSigner(key any) (crypto.Signer, error) {
|
||||
// The ed25519.PrivateKey object implements crypto.Signer, so we should
|
||||
// simply accept a crypto.Signer here.
|
||||
signer, ok := key.(crypto.Signer)
|
||||
if ok {
|
||||
if !isValidEDDSAKey(key) {
|
||||
return nil, fmt.Errorf(`invalid key type %T for EdDSA algorithm`, key)
|
||||
}
|
||||
return signer, nil
|
||||
}
|
||||
|
||||
// This fallback exists for cases when users give us a pointer instead of non-pointer, etc.
|
||||
privkey, ok := key.(ed25519.PrivateKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`failed to retrieve ed25519.PrivateKey out of %T`, key)
|
||||
}
|
||||
return privkey, nil
|
||||
}
|
||||
|
||||
// SignEdDSA generates an EdDSA (Ed25519) signature for the given payload.
|
||||
// The raw parameter should be the pre-computed signing input (typically header.payload).
|
||||
// EdDSA is deterministic and doesn't require additional hashing of the input.
|
||||
func SignEdDSA(key ed25519.PrivateKey, payload []byte) ([]byte, error) {
|
||||
return ed25519.Sign(key, payload), nil
|
||||
}
|
||||
|
||||
// VerifyEdDSA verifies an EdDSA (Ed25519) signature for the given payload.
|
||||
// This function verifies the signature using Ed25519 verification algorithm.
|
||||
// The payload parameter should be the pre-computed signing input (typically header.payload).
|
||||
// EdDSA is deterministic and provides strong security guarantees without requiring hash function selection.
|
||||
func VerifyEdDSA(key ed25519.PublicKey, payload, signature []byte) error {
|
||||
if !ed25519.Verify(key, payload, signature) {
|
||||
return fmt.Errorf("invalid EdDSA signature")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"fmt"
|
||||
"hash"
|
||||
)
|
||||
|
||||
func toHMACKey(dst *[]byte, key any) error {
|
||||
keyBytes, ok := key.([]byte)
|
||||
if !ok {
|
||||
return fmt.Errorf(`dsig.toHMACKey: invalid key type %T. []byte is required`, key)
|
||||
}
|
||||
|
||||
if len(keyBytes) == 0 {
|
||||
return fmt.Errorf(`dsig.toHMACKey: missing key while signing payload`)
|
||||
}
|
||||
|
||||
*dst = keyBytes
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignHMAC generates an HMAC signature for the given payload using the specified hash function and key.
|
||||
// The raw parameter should be the pre-computed signing input (typically header.payload).
|
||||
func SignHMAC(key, payload []byte, hfunc func() hash.Hash) ([]byte, error) {
|
||||
h := hmac.New(hfunc, key)
|
||||
if _, err := h.Write(payload); err != nil {
|
||||
return nil, fmt.Errorf(`failed to write payload using hmac: %w`, err)
|
||||
}
|
||||
return h.Sum(nil), nil
|
||||
}
|
||||
|
||||
// VerifyHMAC verifies an HMAC signature for the given payload.
|
||||
// This function verifies the signature using the specified key and hash function.
|
||||
// The payload parameter should be the pre-computed signing input (typically header.payload).
|
||||
func VerifyHMAC(key, payload, signature []byte, hfunc func() hash.Hash) error {
|
||||
expected, err := SignHMAC(key, payload, hfunc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to sign payload for verification: %w", err)
|
||||
}
|
||||
if !hmac.Equal(signature, expected) {
|
||||
return NewVerificationError("invalid HMAC signature")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
// Package ecutil defines tools that help with elliptic curve related
|
||||
// computation
|
||||
package ecutil
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"math/big"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// size of buffer that needs to be allocated for EC521 curve
|
||||
ec521BufferSize = 66 // (521 / 8) + 1
|
||||
)
|
||||
|
||||
var ecpointBufferPool = sync.Pool{
|
||||
New: func() any {
|
||||
// In most cases the curve bit size will be less than this length
|
||||
// so allocate the maximum, and keep reusing
|
||||
buf := make([]byte, 0, ec521BufferSize)
|
||||
return &buf
|
||||
},
|
||||
}
|
||||
|
||||
func getCrvFixedBuffer(size int) []byte {
|
||||
//nolint:forcetypeassert
|
||||
buf := *(ecpointBufferPool.Get().(*[]byte))
|
||||
if size > ec521BufferSize && cap(buf) < size {
|
||||
buf = append(buf, make([]byte, size-cap(buf))...)
|
||||
}
|
||||
return buf[:size]
|
||||
}
|
||||
|
||||
// ReleaseECPointBuffer releases the []byte buffer allocated.
|
||||
func ReleaseECPointBuffer(buf []byte) {
|
||||
buf = buf[:cap(buf)]
|
||||
buf[0] = 0x0
|
||||
for i := 1; i < len(buf); i *= 2 {
|
||||
copy(buf[i:], buf[:i])
|
||||
}
|
||||
buf = buf[:0]
|
||||
ecpointBufferPool.Put(&buf)
|
||||
}
|
||||
|
||||
func CalculateKeySize(crv elliptic.Curve) int {
|
||||
// We need to create a buffer that fits the entire curve.
|
||||
// If the curve size is 66, that fits in 9 bytes. If the curve
|
||||
// size is 64, it fits in 8 bytes.
|
||||
bits := crv.Params().BitSize
|
||||
|
||||
// For most common cases we know before hand what the byte length
|
||||
// is going to be. optimize
|
||||
var inBytes int
|
||||
switch bits {
|
||||
case 224, 256, 384: // TODO: use constant?
|
||||
inBytes = bits / 8
|
||||
case 521:
|
||||
inBytes = ec521BufferSize
|
||||
default:
|
||||
inBytes = bits / 8
|
||||
if (bits % 8) != 0 {
|
||||
inBytes++
|
||||
}
|
||||
}
|
||||
|
||||
return inBytes
|
||||
}
|
||||
|
||||
// AllocECPointBuffer allocates a buffer for the given point in the given
|
||||
// curve. This buffer should be released using the ReleaseECPointBuffer
|
||||
// function.
|
||||
func AllocECPointBuffer(v *big.Int, crv elliptic.Curve) []byte {
|
||||
buf := getCrvFixedBuffer(CalculateKeySize(crv))
|
||||
v.FillBytes(buf)
|
||||
return buf
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
func rsaGetSignerCryptoSignerKey(key any) (crypto.Signer, bool, error) {
|
||||
if !isValidRSAKey(key) {
|
||||
return nil, false, fmt.Errorf(`invalid key type %T for RSA algorithm`, key)
|
||||
}
|
||||
cs, isCryptoSigner := key.(crypto.Signer)
|
||||
if isCryptoSigner {
|
||||
return cs, true, nil
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// rsaPSSOptions returns the PSS options for RSA-PSS signatures with the specified hash.
|
||||
// The salt length is set to equal the hash length as per RFC 7518.
|
||||
func rsaPSSOptions(h crypto.Hash) rsa.PSSOptions {
|
||||
return rsa.PSSOptions{
|
||||
Hash: h,
|
||||
SaltLength: rsa.PSSSaltLengthEqualsHash,
|
||||
}
|
||||
}
|
||||
|
||||
// SignRSA generates an RSA signature for the given payload using the specified private key and options.
|
||||
// The raw parameter should be the pre-computed signing input (typically header.payload).
|
||||
// If pss is true, RSA-PSS is used; otherwise, PKCS#1 v1.5 is used.
|
||||
//
|
||||
// The rr parameter is an optional io.Reader that can be used to provide randomness for signing.
|
||||
// If rr is nil, it defaults to rand.Reader.
|
||||
func SignRSA(key *rsa.PrivateKey, payload []byte, h crypto.Hash, pss bool, rr io.Reader) ([]byte, error) {
|
||||
if !isValidRSAKey(key) {
|
||||
return nil, fmt.Errorf(`invalid key type %T for RSA algorithm`, key)
|
||||
}
|
||||
var opts crypto.SignerOpts = h
|
||||
if pss {
|
||||
rsaopts := rsaPSSOptions(h)
|
||||
opts = &rsaopts
|
||||
}
|
||||
return cryptosign(key, payload, h, opts, rr)
|
||||
}
|
||||
|
||||
// VerifyRSA verifies an RSA signature for the given payload and header.
|
||||
// This function constructs the signing input by encoding the header and payload according to JWS specification,
|
||||
// then verifies the signature using the specified public key and hash algorithm.
|
||||
// If pss is true, RSA-PSS verification is used; otherwise, PKCS#1 v1.5 verification is used.
|
||||
func VerifyRSA(key *rsa.PublicKey, payload, signature []byte, h crypto.Hash, pss bool) error {
|
||||
if !isValidRSAKey(key) {
|
||||
return fmt.Errorf(`invalid key type %T for RSA algorithm`, key)
|
||||
}
|
||||
hasher := h.New()
|
||||
hasher.Write(payload)
|
||||
digest := hasher.Sum(nil)
|
||||
if pss {
|
||||
return rsa.VerifyPSS(key, h, digest, signature, &rsa.PSSOptions{Hash: h, SaltLength: rsa.PSSSaltLengthEqualsHash})
|
||||
}
|
||||
return rsa.VerifyPKCS1v15(key, h, digest, signature)
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Sign generates a digital signature using the specified key and algorithm.
|
||||
//
|
||||
// This function loads the signer registered in the dsig package _ONLY_.
|
||||
// It does not support custom signers that the user might have registered.
|
||||
//
|
||||
// rr is an io.Reader that provides randomness for signing. If rr is nil, it defaults to rand.Reader.
|
||||
// Not all algorithms require this parameter, but it is included for consistency.
|
||||
// 99% of the time, you can pass nil for rr, and it will work fine.
|
||||
func Sign(key any, alg string, payload []byte, rr io.Reader) ([]byte, error) {
|
||||
info, ok := GetAlgorithmInfo(alg)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`dsig.Sign: unsupported signature algorithm %q`, alg)
|
||||
}
|
||||
|
||||
switch info.Family {
|
||||
case HMAC:
|
||||
return dispatchHMACSign(key, info, payload)
|
||||
case RSA:
|
||||
return dispatchRSASign(key, info, payload, rr)
|
||||
case ECDSA:
|
||||
return dispatchECDSASign(key, info, payload, rr)
|
||||
case EdDSAFamily:
|
||||
return dispatchEdDSASign(key, info, payload, rr)
|
||||
default:
|
||||
return nil, fmt.Errorf(`dsig.Sign: unsupported signature family %q`, info.Family)
|
||||
}
|
||||
}
|
||||
|
||||
func dispatchHMACSign(key any, info AlgorithmInfo, payload []byte) ([]byte, error) {
|
||||
meta, ok := info.Meta.(HMACFamilyMeta)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`dsig.Sign: invalid HMAC metadata`)
|
||||
}
|
||||
|
||||
var hmackey []byte
|
||||
if err := toHMACKey(&hmackey, key); err != nil {
|
||||
return nil, fmt.Errorf(`dsig.Sign: %w`, err)
|
||||
}
|
||||
return SignHMAC(hmackey, payload, meta.HashFunc)
|
||||
}
|
||||
|
||||
func dispatchRSASign(key any, info AlgorithmInfo, payload []byte, rr io.Reader) ([]byte, error) {
|
||||
meta, ok := info.Meta.(RSAFamilyMeta)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`dsig.Sign: invalid RSA metadata`)
|
||||
}
|
||||
|
||||
cs, isCryptoSigner, err := rsaGetSignerCryptoSignerKey(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`dsig.Sign: %w`, err)
|
||||
}
|
||||
if isCryptoSigner {
|
||||
var options crypto.SignerOpts = meta.Hash
|
||||
if meta.PSS {
|
||||
rsaopts := rsaPSSOptions(meta.Hash)
|
||||
options = &rsaopts
|
||||
}
|
||||
return SignCryptoSigner(cs, payload, meta.Hash, options, rr)
|
||||
}
|
||||
|
||||
privkey, ok := key.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`dsig.Sign: invalid key type %T. *rsa.PrivateKey is required`, key)
|
||||
}
|
||||
return SignRSA(privkey, payload, meta.Hash, meta.PSS, rr)
|
||||
}
|
||||
|
||||
func dispatchEdDSASign(key any, _ AlgorithmInfo, payload []byte, rr io.Reader) ([]byte, error) {
|
||||
signer, err := eddsaGetSigner(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`dsig.Sign: %w`, err)
|
||||
}
|
||||
|
||||
return SignCryptoSigner(signer, payload, crypto.Hash(0), crypto.Hash(0), rr)
|
||||
}
|
||||
|
||||
func dispatchECDSASign(key any, info AlgorithmInfo, payload []byte, rr io.Reader) ([]byte, error) {
|
||||
meta, ok := info.Meta.(ECDSAFamilyMeta)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`dsig.Sign: invalid ECDSA metadata`)
|
||||
}
|
||||
|
||||
privkey, cs, isCryptoSigner, err := ecdsaGetSignerKey(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`dsig.Sign: %w`, err)
|
||||
}
|
||||
if isCryptoSigner {
|
||||
return SignECDSACryptoSigner(cs, payload, meta.Hash, rr)
|
||||
}
|
||||
return SignECDSA(privkey, payload, meta.Hash, rr)
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
)
|
||||
|
||||
// isValidRSAKey validates that the provided key type is appropriate for RSA algorithms.
|
||||
// It returns false if the key is clearly incompatible (e.g., ECDSA or EdDSA keys).
|
||||
func isValidRSAKey(key any) bool {
|
||||
switch key.(type) {
|
||||
case
|
||||
ecdsa.PrivateKey, *ecdsa.PrivateKey,
|
||||
ed25519.PrivateKey:
|
||||
// these are NOT ok for RSA algorithms
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// isValidECDSAKey validates that the provided key type is appropriate for ECDSA algorithms.
|
||||
// It returns false if the key is clearly incompatible (e.g., RSA or EdDSA keys).
|
||||
func isValidECDSAKey(key any) bool {
|
||||
switch key.(type) {
|
||||
case
|
||||
ed25519.PrivateKey,
|
||||
rsa.PrivateKey, *rsa.PrivateKey:
|
||||
// these are NOT ok for ECDSA algorithms
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// isValidEDDSAKey validates that the provided key type is appropriate for EdDSA algorithms.
|
||||
// It returns false if the key is clearly incompatible (e.g., RSA or ECDSA keys).
|
||||
func isValidEDDSAKey(key any) bool {
|
||||
switch key.(type) {
|
||||
case
|
||||
ecdsa.PrivateKey, *ecdsa.PrivateKey,
|
||||
rsa.PrivateKey, *rsa.PrivateKey:
|
||||
// these are NOT ok for EdDSA algorithms
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// VerificationError represents an error that occurred during signature verification.
|
||||
type VerificationError struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func (e *VerificationError) Error() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
// NewVerificationError creates a new verification error with the given message.
|
||||
func NewVerificationError(message string) error {
|
||||
return &VerificationError{message: message}
|
||||
}
|
||||
|
||||
// IsVerificationError checks if the given error is a verification error.
|
||||
func IsVerificationError(err error) bool {
|
||||
_, ok := err.(*VerificationError)
|
||||
return ok
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package dsig
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Verify verifies a digital signature using the specified key and algorithm.
|
||||
//
|
||||
// This function loads the verifier registered in the dsig package _ONLY_.
|
||||
// It does not support custom verifiers that the user might have registered.
|
||||
func Verify(key any, alg string, payload, signature []byte) error {
|
||||
info, ok := GetAlgorithmInfo(alg)
|
||||
if !ok {
|
||||
return fmt.Errorf(`dsig.Verify: unsupported signature algorithm %q`, alg)
|
||||
}
|
||||
|
||||
switch info.Family {
|
||||
case HMAC:
|
||||
return dispatchHMACVerify(key, info, payload, signature)
|
||||
case RSA:
|
||||
return dispatchRSAVerify(key, info, payload, signature)
|
||||
case ECDSA:
|
||||
return dispatchECDSAVerify(key, info, payload, signature)
|
||||
case EdDSAFamily:
|
||||
return dispatchEdDSAVerify(key, info, payload, signature)
|
||||
default:
|
||||
return fmt.Errorf(`dsig.Verify: unsupported signature family %q`, info.Family)
|
||||
}
|
||||
}
|
||||
|
||||
func dispatchHMACVerify(key any, info AlgorithmInfo, payload, signature []byte) error {
|
||||
meta, ok := info.Meta.(HMACFamilyMeta)
|
||||
if !ok {
|
||||
return fmt.Errorf(`dsig.Verify: invalid HMAC metadata`)
|
||||
}
|
||||
|
||||
var hmackey []byte
|
||||
if err := toHMACKey(&hmackey, key); err != nil {
|
||||
return fmt.Errorf(`dsig.Verify: %w`, err)
|
||||
}
|
||||
return VerifyHMAC(hmackey, payload, signature, meta.HashFunc)
|
||||
}
|
||||
|
||||
func dispatchRSAVerify(key any, info AlgorithmInfo, payload, signature []byte) error {
|
||||
meta, ok := info.Meta.(RSAFamilyMeta)
|
||||
if !ok {
|
||||
return fmt.Errorf(`dsig.Verify: invalid RSA metadata`)
|
||||
}
|
||||
|
||||
var pubkey *rsa.PublicKey
|
||||
|
||||
if cs, ok := key.(crypto.Signer); ok {
|
||||
cpub := cs.Public()
|
||||
switch cpub := cpub.(type) {
|
||||
case rsa.PublicKey:
|
||||
pubkey = &cpub
|
||||
case *rsa.PublicKey:
|
||||
pubkey = cpub
|
||||
default:
|
||||
return fmt.Errorf(`dsig.Verify: failed to retrieve rsa.PublicKey out of crypto.Signer %T`, key)
|
||||
}
|
||||
} else {
|
||||
var ok bool
|
||||
pubkey, ok = key.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf(`dsig.Verify: failed to retrieve *rsa.PublicKey out of %T`, key)
|
||||
}
|
||||
}
|
||||
|
||||
return VerifyRSA(pubkey, payload, signature, meta.Hash, meta.PSS)
|
||||
}
|
||||
|
||||
func dispatchECDSAVerify(key any, info AlgorithmInfo, payload, signature []byte) error {
|
||||
meta, ok := info.Meta.(ECDSAFamilyMeta)
|
||||
if !ok {
|
||||
return fmt.Errorf(`dsig.Verify: invalid ECDSA metadata`)
|
||||
}
|
||||
|
||||
pubkey, cs, isCryptoSigner, err := ecdsaGetVerifierKey(key)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`dsig.Verify: %w`, err)
|
||||
}
|
||||
if isCryptoSigner {
|
||||
return VerifyECDSACryptoSigner(cs, payload, signature, meta.Hash)
|
||||
}
|
||||
return VerifyECDSA(pubkey, payload, signature, meta.Hash)
|
||||
}
|
||||
|
||||
func dispatchEdDSAVerify(key any, _ AlgorithmInfo, payload, signature []byte) error {
|
||||
var pubkey ed25519.PublicKey
|
||||
signer, ok := key.(crypto.Signer)
|
||||
if ok {
|
||||
v := signer.Public()
|
||||
pubkey, ok = v.(ed25519.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf(`dsig.Verify: expected crypto.Signer.Public() to return ed25519.PublicKey, but got %T`, v)
|
||||
}
|
||||
} else {
|
||||
var ok bool
|
||||
pubkey, ok = key.(ed25519.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf(`dsig.Verify: failed to retrieve ed25519.PublicKey out of %T`, key)
|
||||
}
|
||||
}
|
||||
|
||||
return VerifyEdDSA(pubkey, payload, signature)
|
||||
}
|
||||
|
||||
func ecdsaGetVerifierKey(key any) (*ecdsa.PublicKey, crypto.Signer, bool, error) {
|
||||
cs, isCryptoSigner := key.(crypto.Signer)
|
||||
if isCryptoSigner {
|
||||
switch key.(type) {
|
||||
case ecdsa.PublicKey, *ecdsa.PublicKey:
|
||||
// if it's ecdsa.PublicKey, it's more efficient to
|
||||
// go through the non-crypto.Signer route. Set isCryptoSigner to false
|
||||
isCryptoSigner = false
|
||||
}
|
||||
}
|
||||
|
||||
if isCryptoSigner {
|
||||
return nil, cs, true, nil
|
||||
}
|
||||
|
||||
pubkey, ok := key.(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, nil, false, fmt.Errorf(`invalid key type %T. *ecdsa.PublicKey is required`, key)
|
||||
}
|
||||
|
||||
return pubkey, nil, false, nil
|
||||
}
|
||||
Reference in New Issue
Block a user