-
Notifications
You must be signed in to change notification settings - Fork 2
fix: allow concurrent sign operations and serialize if not possible #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
jakobmoellerdev
merged 22 commits into
open-component-model:main
from
jakobmoellerdev:concurrent-sign
Jun 26, 2025
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
5135601
fix: allow concurrent sign operations and serialize if not possible
jakobmoellerdev fa2a794
chore(ci): add ci
jakobmoellerdev 1a2d03d
chore(ci): add ci
jakobmoellerdev 1a02c6c
chore(ci): add ci
jakobmoellerdev 89fa888
chore(ci): add ci
jakobmoellerdev 845702c
chore(ci): add ci
jakobmoellerdev 7457057
chore(ci): add ci
jakobmoellerdev 88f7e1d
chore(ci): add ci
jakobmoellerdev 8a1e867
chore(ci): add ci
jakobmoellerdev ba8247b
chore(ci): add ci
jakobmoellerdev 7cec711
chore(ci): add ci
jakobmoellerdev bf81241
chore(ci): add ci
jakobmoellerdev 140de39
chore(ci): add ci
jakobmoellerdev b8396ae
chore(ci): add ci
jakobmoellerdev 53a0e99
chore(ci): add ci
jakobmoellerdev b47e4df
chore(ci): add ci
jakobmoellerdev 0024774
chore(ci): add ci
jakobmoellerdev 060d39c
chore(ci): add ci
jakobmoellerdev 7a0e1c3
chore(ci): add ci
jakobmoellerdev d17a80e
chore(ci): add ci
jakobmoellerdev bb33b37
chore(ci): add ci
jakobmoellerdev 3d59a15
chore(ci): add ci
jakobmoellerdev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| name: CI | ||
| on: [push, pull_request] | ||
| jobs: | ||
| hsm: | ||
| env: | ||
| HSM_SO_PIN: 1234 | ||
| HSM_PIN: 1234 | ||
| TOKEN_LABEL: 'test' | ||
| KEY_LABEL: 'test-key' | ||
| CGO_ENABLED: 1 | ||
| name: Build and Test | ||
| runs-on: ubuntu-latest | ||
| strategy: | ||
| matrix: | ||
| go: [ 1.24.x ] | ||
| steps: | ||
|
|
||
| - name: Set up Go | ||
| uses: actions/setup-go@v2 | ||
| with: | ||
| go-version: ${{ matrix.go }} | ||
|
|
||
| - name: Check out code | ||
| uses: actions/checkout@v2 | ||
|
|
||
| - name: Setup SoftHSM | ||
| env: | ||
| SOFTHSM2_CONF: ${{ github.workspace }}/softhsm2.conf | ||
| id: softhsm | ||
| run: | | ||
| mkdir ${GITHUB_WORKSPACE}/softhsm2-tokens | ||
| # 2) custom SoftHSM config that uses it | ||
| cat > "${SOFTHSM2_CONF}" <<EOF | ||
| directories.tokendir = ${GITHUB_WORKSPACE}/softhsm2-tokens | ||
| objectstore.backend = file | ||
| log.level = INFO | ||
| EOF | ||
| echo "SOFTHSM2_CONF=${SOFTHSM2_CONF}" >> "${GITHUB_ENV}" # make it stick for later steps | ||
|
|
||
| sudo apt-get update | ||
| sudo apt-get -y install libsofthsm2 gnutls-bin p11-kit | ||
|
|
||
| # set output of lib to environment variable | ||
|
|
||
| softhsm2-util --init-token --free --label $TOKEN_LABEL --so-pin $HSM_SO_PIN --pin $HSM_PIN | ||
| p11tool --generate-privkey=rsa --login --set-pin=$HSM_PIN --label="$KEY_LABEL" "pkcs11:token=$TOKEN_LABEL" --outfile ${{ github.workspace }}/public_key.pem | ||
|
|
||
| p11-kit list-modules | ||
| - name: Run Tests | ||
| working-directory: ${{ github.workspace }} | ||
| env: | ||
| HSM_MODULE: "/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so" | ||
| SIGNING_SERVER_BIN: ${{ github.workspace }}/signing-server | ||
| HSM_PUBLIC_KEY_FILE: ${{ github.workspace }}/public_key.pem | ||
| SOFTHSM2_CONF: ${{ github.workspace }}/softhsm2.conf | ||
| run: | | ||
| go test -v ./... | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,186 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "context" | ||
| "crypto" | ||
| "crypto/rsa" | ||
| "crypto/x509" | ||
| "encoding/hex" | ||
| "encoding/pem" | ||
| "fmt" | ||
| "io" | ||
| "log" | ||
| "net/http" | ||
| "os" | ||
| "strings" | ||
| "sync" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "go.uber.org/zap" | ||
| ) | ||
|
|
||
| func TestSoftHSMConcurrentSignRequests(t *testing.T) { | ||
| hsmModule := os.Getenv("HSM_MODULE") | ||
| if hsmModule == "" { | ||
| t.Skip("HSM_MODULE environment variable is not set") | ||
| } | ||
| tokenLabel := os.Getenv("TOKEN_LABEL") | ||
| if tokenLabel == "" { | ||
| t.Skip("TOKEN_LABEL environment variable is not set") | ||
| } | ||
| keyLabel := os.Getenv("KEY_LABEL") | ||
| if keyLabel == "" { | ||
| t.Skip("KEY_LABEL environment variable is not set") | ||
| } | ||
| hsmPin := os.Getenv("HSM_PIN") | ||
| if hsmPin == "" { | ||
| t.Skip("HSM_PIN environment variable is not set") | ||
| } | ||
|
|
||
| const ( | ||
| base = "http://localhost:8080" | ||
| healthURL = base + "/healthz" | ||
| url = base + "/sign/rsassa-pss?hashAlgorithm=sha256" | ||
| bodyHex = "aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f" | ||
| ) | ||
|
|
||
| var ( | ||
| headers = map[string]string{ | ||
| "Content-Type": "text/plain", | ||
| "Content-Encoding": "hex", | ||
| "Accept": "application/x-pem-file", | ||
| } | ||
| ) | ||
|
|
||
| l, err := zap.NewDevelopment() | ||
| if err != nil { | ||
| t.Fatalf("Failed to create logger: %v", err) | ||
| } | ||
|
|
||
| ctx, cancel := context.WithCancel(t.Context()) | ||
| t.Cleanup(func() { | ||
| cancel() | ||
| }) | ||
|
|
||
| go func() { | ||
| if err := run(&Config{ | ||
| HSMModule: strings.TrimSpace(hsmModule), | ||
| HSMTokenLabel: tokenLabel, | ||
| HSMSlot: -1, | ||
| HSMKeyLabel: keyLabel, | ||
| HSMPass: hsmPin, | ||
| Port: "8080", | ||
| DisableHTTPS: true, | ||
| DisableAuth: true, | ||
| MaxBodySizeBytes: 2048, | ||
| Logger: l, | ||
| RunServer: true, | ||
| }); err != nil { | ||
| cancel() | ||
| t.Errorf("Failed to start signing server: %v", err) | ||
| return | ||
| } | ||
| }() | ||
|
|
||
| var wg sync.WaitGroup | ||
| client := &http.Client{Timeout: 10 * time.Second} | ||
|
|
||
| outer: | ||
| for { | ||
| select { | ||
| case <-ctx.Done(): | ||
| t.Fatal("Test context was cancelled before health check passed") | ||
| default: | ||
| log.Println("Waiting for signing server to be ready...") | ||
| healthReq, err := http.NewRequest(http.MethodGet, healthURL, nil) | ||
| if err != nil { | ||
| t.Fatalf("Health request creation failed: %v", err) | ||
| } | ||
| healthResp, _ := client.Do(healthReq) | ||
| if healthResp != nil && healthResp.StatusCode == http.StatusOK { | ||
| log.Println("Health check passed") | ||
| break outer | ||
| } | ||
| time.Sleep(500 * time.Millisecond) | ||
| } | ||
| } | ||
|
|
||
| for i := 0; i < 10; i++ { | ||
| wg.Add(1) | ||
|
|
||
| go func(index int) { | ||
| defer wg.Done() | ||
|
|
||
| req, err := http.NewRequest(http.MethodPost, url, bytes.NewBufferString(bodyHex)) | ||
| if err != nil { | ||
| t.Errorf("Request %d creation failed: %v", index, err) | ||
| return | ||
| } | ||
|
|
||
| for k, v := range headers { | ||
| req.Header.Set(k, v) | ||
| } | ||
|
|
||
| resp, err := client.Do(req) | ||
| if err != nil { | ||
| t.Errorf("Request %d failed: %v", index, err) | ||
| return | ||
| } | ||
| defer resp.Body.Close() | ||
|
|
||
| body, err := io.ReadAll(resp.Body) | ||
| if err != nil { | ||
| t.Errorf("Request %d read body failed: %v", index, err) | ||
| return | ||
| } | ||
| if resp.StatusCode != http.StatusOK { | ||
| t.Errorf("Request %d failed with status %d: %s", index, resp.StatusCode, body) | ||
| return | ||
| } | ||
|
|
||
| block, _ := pem.Decode(body) | ||
| if block == nil || block.Type != "SIGNATURE" { | ||
| log.Fatal("Failed to parse PEM block") | ||
| } | ||
|
|
||
| hsmPublicKeyFile := os.Getenv("HSM_PUBLIC_KEY_FILE") | ||
| if hsmPublicKeyFile == "" { | ||
| log.Println("HSM_PUBLIC_KEY_FILE environment variable is not set, skipping public key verification") | ||
| } else { | ||
| hsmPublicKeyData, err := os.ReadFile(hsmPublicKeyFile) | ||
| if err != nil { | ||
| log.Fatalf("Failed to read public key file %s: %v", hsmPublicKeyFile, err) | ||
| } | ||
| publicKeyBlock, _ := pem.Decode(hsmPublicKeyData) | ||
| if publicKeyBlock == nil || publicKeyBlock.Type != "PUBLIC KEY" { | ||
| log.Fatal("Failed to parse PEM block for public key") | ||
| } | ||
| pub, err := x509.ParsePKIXPublicKey(publicKeyBlock.Bytes) | ||
| if err != nil { | ||
| log.Fatalf("Failed to parse public key: %v", err) | ||
| } | ||
| rsaPubKey, ok := pub.(*rsa.PublicKey) | ||
| if !ok { | ||
| log.Fatal("Public key is not of type RSA") | ||
| } | ||
| rawDigest, err := hex.DecodeString(bodyHex) | ||
| if err != nil { | ||
| log.Fatalf("Failed to decode hex body: %v", err) | ||
| } | ||
| if err := rsa.VerifyPSS(rsaPubKey, crypto.SHA256, rawDigest, block.Bytes, nil); err != nil { | ||
| log.Fatalf("Signature verification failed: %v", err) | ||
| } else { | ||
| log.Printf("Signature verification succeeded for request %d", index) | ||
| } | ||
| } | ||
|
|
||
| fmt.Printf("Decoded signature: %x\n", block.Bytes) | ||
|
|
||
| log.Printf("Response %d: %d - %.100q\n", index, resp.StatusCode, body) | ||
| }(i) | ||
| } | ||
|
|
||
| wg.Wait() | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.