pax_global_header00006660000000000000000000000064150506035740014517gustar00rootroot0000000000000052 comment=e7e4d54deeba321a433dfe959340ebe7a2db163d lestrrat-go-dsig-e7e4d54/000077500000000000000000000000001505060357400153315ustar00rootroot00000000000000lestrrat-go-dsig-e7e4d54/.github/000077500000000000000000000000001505060357400166715ustar00rootroot00000000000000lestrrat-go-dsig-e7e4d54/.github/workflows/000077500000000000000000000000001505060357400207265ustar00rootroot00000000000000lestrrat-go-dsig-e7e4d54/.github/workflows/autodoc.yml000066400000000000000000000010271505060357400231070ustar00rootroot00000000000000name: Auto-Doc on: pull_request: branches: - main types: - closed jobs: autodoc: runs-on: ubuntu-latest name: "Run commands to generate documentation" if: github.event.pull_request.merged == true steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Process markdown files run: | find . -name '*.md' | xargs perl tools/autodoc.pl env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}lestrrat-go-dsig-e7e4d54/.github/workflows/ci.yml000066400000000000000000000023321505060357400220440ustar00rootroot00000000000000name: CI on: push: branches: - main pull_request: branches: - main jobs: build: runs-on: ubuntu-latest strategy: matrix: go: [ '1.24', '1.23' ] name: "Test [ Go ${{ matrix.go }} ]" steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Cache Go modules uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: | ~/go/pkg/mod ~/.cache/go-build key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: Install Go stable version uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 with: go-version: ${{ matrix.go }} check-latest: true - name: Run go mod tidy run: go mod tidy - name: Run tests run: go test -v -race -coverprofile=coverage.out ./... - name: Upload coverage to Codecov if: matrix.go == '1.24' uses: codecov/codecov-action@v5 with: file: ./coverage.out flags: unittests name: codecov-umbrellalestrrat-go-dsig-e7e4d54/.github/workflows/codeql.yml000066400000000000000000000056501505060357400227260ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ main ] pull_request: # The branches below must be a subset of the branches above branches: [ main ] schedule: - cron: '40 13 * * 5' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'go' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Use only 'java' to analyze code written in Java, Kotlin or both # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v3 # â„šī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # If the Autobuild fails above, remove it and uncomment the following three lines. # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. # - run: | # echo "Run, Build Application using script" # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}"lestrrat-go-dsig-e7e4d54/.github/workflows/dependabot.yml000066400000000000000000000017071505060357400235630ustar00rootroot00000000000000name: merge dependabot on: pull_request: types: [labeled] jobs: merge: if: ${{github.event.label.name == 'dependabot'}} runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 with: go-version-file: "go.mod" - run: | go mod tidy - run: | go test ./... - run: | git config --local user.name 'github-actions[bot]' git config --local user.email '41898282+github-actions[bot]@users.noreply.github.com' git add . git commit -m "Run go mod tidy" || exit 0 git push origin "HEAD:${GITHUB_HEAD_REF}" gh pr review --approve "$PR_URL" gh pr merge --auto --merge "$PR_URL" env: PR_URL: ${{github.event.pull_request.html_url}} GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}lestrrat-go-dsig-e7e4d54/.github/workflows/lint.yml000066400000000000000000000007151505060357400224220ustar00rootroot00000000000000name: lint on: [push] jobs: golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 with: go-version-file: "go.mod" - uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 - name: Run go vet run: | go vet ./...lestrrat-go-dsig-e7e4d54/.github/workflows/smoke.yml000066400000000000000000000024571505060357400225770ustar00rootroot00000000000000# Smoke tests only run on non-main branches. Smoke tests cut # some corners by running selected tests in parallel (to shave off # some execution time) # Once a pull request is merged to main, workflows/ci.yml is run name: Smoke Tests on: push: branches-ignore: - main jobs: build: runs-on: ubuntu-latest strategy: matrix: go: [ '1.24', '1.23' ] name: "Smoke [ Go ${{ matrix.go }} ]" steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Check documentation generator run: | find . -name '*.md' | xargs env AUTODOC_DRYRUN=1 perl tools/autodoc.pl - name: Cache Go modules uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: | ~/go/pkg/mod ~/.cache/go-build key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: Install Go stable version uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 with: go-version: ${{ matrix.go }} check-latest: true - name: make tidy run: go mod tidy - name: Run smoke tests run: go test -v -race ./...lestrrat-go-dsig-e7e4d54/.github/workflows/stale.yml000066400000000000000000000025451505060357400225670ustar00rootroot00000000000000name: 'Close stale issues and PRs' on: schedule: - cron: '30 1 * * *' jobs: stale: runs-on: ubuntu-latest steps: - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 with: stale-issue-message: 'This issue is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 7 days.' stale-pr-message: 'This PR is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 14 days.' close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity. This does not mean your issue is rejected, but rather it is done to hide it from the view of the maintains for the time being. Feel free to reopen if you have new comments' close-pr-message: 'This PR was closed because it has been stalled for 14 days with no activity. This does not mean your PR is rejected, but rather it is done to hide it from the view of the maintainers for the time being. Feel free to reopen if you have new comments or changes that you would like to include. ' days-before-issue-stale: 14 days-before-pr-stale: 14 days-before-issue-close: 7 days-before-pr-close: 7 exempt-issue-labels: long-term exempt-pr-labels: long-termlestrrat-go-dsig-e7e4d54/.gitignore000066400000000000000000000010571505060357400173240ustar00rootroot00000000000000# 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/ lestrrat-go-dsig-e7e4d54/Changes000066400000000000000000000000671505060357400166270ustar00rootroot00000000000000Changes ======= v1.0.0 - 18 Aug 2025 * Initial releaselestrrat-go-dsig-e7e4d54/LICENSE000066400000000000000000000020541505060357400163370ustar00rootroot00000000000000MIT 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. lestrrat-go-dsig-e7e4d54/README.md000066400000000000000000000125501505060357400166130ustar00rootroot00000000000000# github.com/lestrrat-go/dsig [![CI](https://github.com/lestrrat-go/dsig/actions/workflows/ci.yml/badge.svg)](https://github.com/lestrrat-go/dsig/actions/workflows/ci.yml) [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/dsig.svg)](https://pkg.go.dev/github.com/lestrrat-go/dsig) [![codecov.io](https://codecov.io/github/lestrrat-go/dsig/coverage.svg?branch=v1)](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 ```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) # 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) implementationlestrrat-go-dsig-e7e4d54/algorithms.go000066400000000000000000000027411505060357400200350ustar00rootroot00000000000000package 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" )lestrrat-go-dsig-e7e4d54/crypto_signer.go000066400000000000000000000027371505060357400205600ustar00rootroot00000000000000package 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) } lestrrat-go-dsig-e7e4d54/dsig.go000066400000000000000000000123261505060357400166120ustar00rootroot00000000000000// 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)) } } } lestrrat-go-dsig-e7e4d54/dsig_test.go000066400000000000000000000115601505060357400176500ustar00rootroot00000000000000package dsig_test import ( "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/sha512" "hash" "testing" "github.com/lestrrat-go/dsig" "github.com/stretchr/testify/require" ) func TestHMAC(t *testing.T) { t.Parallel() tests := []struct { alg string hfunc func() hash.Hash }{ {dsig.HMACWithSHA256, sha256.New}, {dsig.HMACWithSHA384, sha512.New384}, {dsig.HMACWithSHA512, sha512.New}, } for _, tc := range tests { t.Run(tc.alg, func(t *testing.T) { payload := []byte("hello world") key := []byte("secretkey") // Test direct HMAC functions sig, err := dsig.SignHMAC(key, payload, tc.hfunc) require.NoError(t, err, "SignHMAC should not return error") require.NoError(t, dsig.VerifyHMAC(key, payload, sig, tc.hfunc), "VerifyHMAC should succeed for a valid signature") require.Error(t, dsig.VerifyHMAC(key, payload, sig[:len(sig)-1], tc.hfunc), "VerifyHMAC should fail for an invalid signature") // Test generic Sign/Verify functions sig2, err := dsig.Sign(key, tc.alg, payload, nil) require.NoError(t, err, "Sign should not return error") require.NoError(t, dsig.Verify(key, tc.alg, payload, sig2), "Verify should succeed for a valid signature") }) } } func TestRSA(t *testing.T) { priv, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "RSA key generation should not error") testcases := []struct { name string alg string h crypto.Hash pss bool }{ {"RSA_PKCS1v15_WITH_SHA256", dsig.RSAPKCS1v15WithSHA256, crypto.SHA256, false}, {"RSA_PKCS1v15_WITH_SHA384", dsig.RSAPKCS1v15WithSHA384, crypto.SHA384, false}, {"RSA_PKCS1v15_WITH_SHA512", dsig.RSAPKCS1v15WithSHA512, crypto.SHA512, false}, {"RSA_PSS_WITH_SHA256", dsig.RSAPSSWithSHA256, crypto.SHA256, true}, {"RSA_PSS_WITH_SHA384", dsig.RSAPSSWithSHA384, crypto.SHA384, true}, {"RSA_PSS_WITH_SHA512", dsig.RSAPSSWithSHA512, crypto.SHA512, true}, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { payload := []byte("hello world") // Test direct RSA functions sig, err := dsig.SignRSA(priv, payload, tc.h, tc.pss, nil) require.NoError(t, err, "SignRSA should not return error") require.NoError(t, dsig.VerifyRSA(&priv.PublicKey, payload, sig, tc.h, tc.pss), "VerifyRSA should succeed for a valid signature") require.Error(t, dsig.VerifyRSA(&priv.PublicKey, payload, sig[:len(sig)-1], tc.h, tc.pss), "VerifyRSA should fail for an invalid signature") // Test generic Sign/Verify functions sig2, err := dsig.Sign(priv, tc.alg, payload, nil) require.NoError(t, err, "Sign should not return error") require.NoError(t, dsig.Verify(&priv.PublicKey, tc.alg, payload, sig2), "Verify should succeed for a valid signature") }) } } func TestECDSA(t *testing.T) { table := []struct { name string alg string curve elliptic.Curve h crypto.Hash }{ {"ECDSA_WITH_P256_AND_SHA256", dsig.ECDSAWithP256AndSHA256, elliptic.P256(), crypto.SHA256}, {"ECDSA_WITH_P384_AND_SHA384", dsig.ECDSAWithP384AndSHA384, elliptic.P384(), crypto.SHA384}, {"ECDSA_WITH_P521_AND_SHA512", dsig.ECDSAWithP521AndSHA512, elliptic.P521(), crypto.SHA512}, } for _, tc := range table { t.Run(tc.name, func(t *testing.T) { payload := []byte("hello world") priv, err := ecdsa.GenerateKey(tc.curve, rand.Reader) require.NoError(t, err, "ECDSA key generation should not error") // Test direct ECDSA functions sig, err := dsig.SignECDSA(priv, payload, tc.h, nil) require.NoError(t, err, "SignECDSA should not return error") require.NoError(t, dsig.VerifyECDSA(&priv.PublicKey, payload, sig, tc.h), "VerifyECDSA should succeed for a valid signature") require.Error(t, dsig.VerifyECDSA(&priv.PublicKey, payload, sig[:len(sig)-1], tc.h), "VerifyECDSA should fail for an invalid signature") // Test generic Sign/Verify functions sig2, err := dsig.Sign(priv, tc.alg, payload, nil) require.NoError(t, err, "Sign should not return error") require.NoError(t, dsig.Verify(&priv.PublicKey, tc.alg, payload, sig2), "Verify should succeed for a valid signature") }) } } func TestEdDSA(t *testing.T) { payload := []byte("hello world") pub, priv, err := ed25519.GenerateKey(rand.Reader) require.NoError(t, err, "EdDSA key generation should not error") // Test direct EdDSA functions sig, err := dsig.SignEdDSA(priv, payload) require.NoError(t, err, "SignEdDSA should not return error") require.NoError(t, dsig.VerifyEdDSA(pub, payload, sig), "VerifyEdDSA should succeed for a valid signature") require.Error(t, dsig.VerifyEdDSA(pub, payload, sig[:len(sig)-1]), "VerifyEdDSA should fail for an invalid signature") // Test generic Sign/Verify functions sig2, err := dsig.Sign(priv, dsig.EdDSA, payload, nil) require.NoError(t, err, "Sign should not return error") require.NoError(t, dsig.Verify(pub, dsig.EdDSA, payload, sig2), "Verify should succeed for a valid signature") } lestrrat-go-dsig-e7e4d54/ecdsa.go000066400000000000000000000152731505060357400167470ustar00rootroot00000000000000package 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) } lestrrat-go-dsig-e7e4d54/eddsa.go000066400000000000000000000030271505060357400167420ustar00rootroot00000000000000package 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 } lestrrat-go-dsig-e7e4d54/examples/000077500000000000000000000000001505060357400171475ustar00rootroot00000000000000lestrrat-go-dsig-e7e4d54/examples/dsig_readme_example_test.go000066400000000000000000000045411505060357400245170ustar00rootroot00000000000000package 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: }lestrrat-go-dsig-e7e4d54/examples/register_curve_test.go000066400000000000000000000034731505060357400235740ustar00rootroot00000000000000package examples_test import ( "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "testing" "github.com/lestrrat-go/dsig" "github.com/stretchr/testify/require" ) func TestRegisterECDSACurve(t *testing.T) { // Define a custom algorithm name customAlg := "CUSTOM_P256_WITH_SHA256" // Register P-256 as a "custom" algorithm (for demonstration) err := dsig.RegisterAlgorithm(customAlg, dsig.AlgorithmInfo{ Family: dsig.ECDSA, Meta: dsig.ECDSAFamilyMeta{ Hash: crypto.SHA256, }, }) require.NoError(t, err) // Test that our custom algorithm now works privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) payload := []byte("test message") // Sign with our custom algorithm sig, err := dsig.Sign(privKey, customAlg, payload, nil) require.NoError(t, err, "Should be able to sign with registered custom algorithm") // Verify with our custom algorithm err = dsig.Verify(&privKey.PublicKey, customAlg, payload, sig) require.NoError(t, err, "Should be able to verify with registered custom algorithm") // Test with different curve - this will work but may not be interoperable // with systems expecting P-256 for this algorithm name p384Key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) require.NoError(t, err) // This will work at the crypto level but may not be what you want for interoperability sig2, err := dsig.Sign(p384Key, customAlg, payload, nil) require.NoError(t, err, "Sign works with any ECDSA key at crypto level") // Verify will work too err = dsig.Verify(&p384Key.PublicKey, customAlg, payload, sig2) require.NoError(t, err, "Verify works with matching curve") // Cross-curve verification will fail naturally err = dsig.Verify(&privKey.PublicKey, customAlg, payload, sig2) require.Error(t, err, "Cross-curve verification fails naturally") }lestrrat-go-dsig-e7e4d54/go.mod000066400000000000000000000004021505060357400164330ustar00rootroot00000000000000module github.com/lestrrat-go/dsig go 1.23.0 toolchain go1.23.6 require github.com/stretchr/testify v1.10.0 require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) lestrrat-go-dsig-e7e4d54/go.sum000066400000000000000000000015631505060357400164710ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= lestrrat-go-dsig-e7e4d54/hmac.go000066400000000000000000000025461505060357400165770ustar00rootroot00000000000000package 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 } lestrrat-go-dsig-e7e4d54/internal/000077500000000000000000000000001505060357400171455ustar00rootroot00000000000000lestrrat-go-dsig-e7e4d54/internal/ecutil/000077500000000000000000000000001505060357400204325ustar00rootroot00000000000000lestrrat-go-dsig-e7e4d54/internal/ecutil/ecutil.go000066400000000000000000000034741505060357400222560ustar00rootroot00000000000000// 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 } lestrrat-go-dsig-e7e4d54/rsa.go000066400000000000000000000042561505060357400164540ustar00rootroot00000000000000package 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) } lestrrat-go-dsig-e7e4d54/sign.go000066400000000000000000000057141505060357400166270ustar00rootroot00000000000000package 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) } lestrrat-go-dsig-e7e4d54/tools/000077500000000000000000000000001505060357400164715ustar00rootroot00000000000000lestrrat-go-dsig-e7e4d54/tools/autodoc.pl000077500000000000000000000054311505060357400204720ustar00rootroot00000000000000#!perl use strict; use File::Temp; # Accept a list of filenames, and process them # if any of them has a diff, commit it # Use GITHUB_REF, but if the ref is develop/v\d, then use v\d my $link_ref = $ENV{GITHUB_REF}; if ($link_ref =~ /^(?:refs\/heads\/)?develop\/(v\d+)$/) { $link_ref = $1; } # Default to v1 if no specific ref $link_ref ||= "v1"; my @files = @ARGV; my @has_diff; for my $filename (@files) { open(my $src, '<', $filename) or die $!; my $output = File::Temp->new(SUFFIX => '.md'); my $skip_until_end; for my $line (<$src>) { if ($line =~ /^$/) { $skip_until_end = 0; } elsif ($skip_until_end) { next; } if ($line !~ /(^)$/) { $output->print($line); next; } $output->print("$1\n"); my $include_filename = $2; my $options = $3; $output->print("```go\n"); my $content = do { open(my $file, '<', $include_filename) or die "failed to include file $include_filename from source file $filename: $!"; local $/; <$file>; }; $content =~ s{^(\t+)}{" " x length($1)}gsme; $output->print($content); $output->print("```\n"); $output->print("source: [$include_filename](https://github.com/lestrrat-go/dsig/blob/$link_ref/$include_filename)\n"); # now we need to skip copying until the end of INCLUDE $skip_until_end = 1; } $output->close(); close($src); if (!$ENV{AUTODOC_DRYRUN}) { rename $output->filename, $filename or die $!; my $diff = `git diff $filename`; if ($diff) { push @has_diff, $filename; } } } if (!$ENV{AUTODOC_DRYRUN}) { if (@has_diff) { # Write multi-line commit message in a file my $commit_message_file = File::Temp->new(SUFFIX => '.txt'); print $commit_message_file "autodoc updates\n\n"; print " - $_\n" for @has_diff; $commit_message_file->close(); system("git", "remote", "set-url", "origin", "https://github-actions:$ENV{GITHUB_TOKEN}\@github.com/$ENV{GITHUB_REPOSITORY}") == 0 or die $!; system("git", "config", "--global", "user.name", "$ENV{GITHUB_ACTOR}") == 0 or die $!; system("git", "config", "--global", "user.email", "$ENV{GITHUB_ACTOR}\@users.noreply.github.com") == 0 or die $!; system("git", "switch", "-c", "autodoc-pr-$ENV{GITHUB_HEAD_REF}") == 0 or die $!; system("git", "commit", "-F", $commit_message_file->filename, @files) == 0 or die $!; system("git", "push", "origin", "HEAD:autodoc-pr-$ENV{GITHUB_HEAD_REF}") == 0 or die $!; system("gh", "pr", "create", "--base", "v1", "--fill") == 0 or die $!; } }lestrrat-go-dsig-e7e4d54/validation.go000066400000000000000000000033331505060357400200140ustar00rootroot00000000000000package 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 } lestrrat-go-dsig-e7e4d54/verify.go000066400000000000000000000072041505060357400171670ustar00rootroot00000000000000package 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 }