Files
probo/pkg/crypto/keys/keys_test.go
Sacha Al Himdani 4c57d201a4 Make license declarations consistently MIT
The source headers, LICENSE files, and license metadata had drifted
apart. Align the entire project to MIT:

- Convert every source-file header to the MIT text across all comment
  styles (Go, TS, TSX, JS, MJS, SQL, CSS, GraphQL, shell), including
  SPDX-License-Identifier tags
- Set the root and cookie-banner LICENSE files to the MIT text with a
  "MIT License" title line
- Switch the package.json license fields, Docker image label, and
  cookie-banner README to MIT
- Update docs and the genmodels header generator accordingly
- Normalize copyright lines to a single format
  (Copyright (c) <year(s)> Probo Inc <hello@probo.com>.): unify the
  hello@getprobo.com and hello@probo.inc emails to hello@probo.com and
  the comma-separated years to a hyphenated range

Genuine third-party references are intentionally left untouched: the
Lucide icon attributions (Lucide is ISC) and the trivy dependency
license allowlist.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
2026-07-13 16:21:14 +02:00

177 lines
4.6 KiB
Go

// Copyright (c) 2025-2026 Probo Inc <hello@probo.com>.
//
// 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.
package keys_test
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/pkg/crypto/keys"
)
func TestGenerate(t *testing.T) {
tests := []struct {
name string
keyType keys.Type
checkFunc func(t *testing.T, key any)
expectError bool
}{
{
name: "EC256",
keyType: keys.TypeEC256,
checkFunc: func(t *testing.T, key any) {
ecKey, ok := key.(*ecdsa.PrivateKey)
require.True(t, ok, "expected *ecdsa.PrivateKey, got %T", key)
assert.Equal(t, elliptic.P256(), ecKey.Curve, "expected P256 curve")
},
},
{
name: "EC384",
keyType: keys.TypeEC384,
checkFunc: func(t *testing.T, key any) {
ecKey, ok := key.(*ecdsa.PrivateKey)
require.True(t, ok, "expected *ecdsa.PrivateKey, got %T", key)
assert.Equal(t, elliptic.P384(), ecKey.Curve, "expected P384 curve")
},
},
{
name: "RSA2048",
keyType: keys.TypeRSA2048,
checkFunc: func(t *testing.T, key any) {
rsaKey, ok := key.(*rsa.PrivateKey)
require.True(t, ok, "expected *rsa.PrivateKey, got %T", key)
bitSize := rsaKey.N.BitLen()
assert.GreaterOrEqual(t, bitSize, 2047, "RSA key too small")
assert.LessOrEqual(t, bitSize, 2048, "RSA key too large")
},
},
{
name: "RSA4096",
keyType: keys.TypeRSA4096,
checkFunc: func(t *testing.T, key any) {
rsaKey, ok := key.(*rsa.PrivateKey)
require.True(t, ok, "expected *rsa.PrivateKey, got %T", key)
bitSize := rsaKey.N.BitLen()
assert.GreaterOrEqual(t, bitSize, 4095, "RSA key too small")
assert.LessOrEqual(t, bitSize, 4096, "RSA key too large")
},
},
{
name: "Invalid key type",
keyType: keys.Type("INVALID"),
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
key, err := keys.Generate(tt.keyType)
if tt.expectError {
assert.Error(t, err, "expected error")
return
}
require.NoError(t, err, "unexpected error")
require.NotNil(t, key, "expected key, got nil")
// Verify the key can be used for signing (crypto.Signer has Public() method)
assert.NotNil(t, key.Public(), "key.Public() returned nil")
if tt.checkFunc != nil {
tt.checkFunc(t, key)
}
})
}
}
func TestGenerateConcurrency(t *testing.T) {
// Test that key generation is safe for concurrent use
keyTypes := []keys.Type{
keys.TypeEC256,
keys.TypeEC384,
keys.TypeRSA2048,
keys.TypeRSA4096,
}
for _, keyType := range keyTypes {
t.Run(string(keyType), func(t *testing.T) {
t.Parallel()
const numGoroutines = 10
errorsChan := make(chan error, numGoroutines)
for range numGoroutines {
go func() {
key, err := keys.Generate(keyType)
if err != nil {
errorsChan <- err
return
}
if key == nil {
errorsChan <- errors.New("generated key is nil")
return
}
errorsChan <- nil
}()
}
for range numGoroutines {
err := <-errorsChan
assert.NoError(t, err, "concurrent generation failed")
}
})
}
}
func BenchmarkGenerate(b *testing.B) {
benchmarks := []struct {
name string
keyType keys.Type
}{
{"EC256", keys.TypeEC256},
{"EC384", keys.TypeEC384},
{"RSA2048", keys.TypeRSA2048},
{"RSA4096", keys.TypeRSA4096},
}
for _, bm := range benchmarks {
b.Run(bm.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, err := keys.Generate(bm.keyType)
if err != nil {
b.Fatal(err)
}
}
})
}
}