Skip to content

Commit 7e577c9

Browse files
committed
review: update crypto tests
1 parent 32bfc74 commit 7e577c9

File tree

3 files changed

+195
-1
lines changed

3 files changed

+195
-1
lines changed

pkg/extensions/webbotauth.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,19 +226,27 @@ func buildWebBotAuthExtension(ctx context.Context, browserExtDir, hostURL, keyDa
226226
pterm.Info.Println("Validating key...")
227227
var pemData []byte
228228
var err error
229+
// JWK data is used for keyid signing in background.ts
230+
var jwkData string
229231

230232
if util.IsPEMKey(keyData) {
231233
// Key is already in PEM format, validate it
232234
if err := util.ValidatePEMKey(keyData); err != nil {
233235
return "", fmt.Errorf("failed to validate PEM key: %w", err)
234236
}
237+
238+
jwkData, err = util.ConvertPEMToJWK(keyData)
239+
if err != nil {
240+
return "", fmt.Errorf("failed to convert PEM to JWK: %w", err)
241+
}
235242
pemData = []byte(keyData)
236243
} else {
237244
// Key is in JWK format, convert to PEM
238245
pemData, err = util.ConvertJWKToPEM(keyData)
239246
if err != nil {
240247
return "", fmt.Errorf("failed to convert JWK to PEM: %w", err)
241248
}
249+
jwkData = keyData
242250
}
243251

244252
privateKeyPath := filepath.Join(browserExtDir, "private_key.pem")
@@ -250,7 +258,7 @@ func buildWebBotAuthExtension(ctx context.Context, browserExtDir, hostURL, keyDa
250258
// Inject the JWK into background.ts (replacing the hardcoded test key)
251259
pterm.Info.Println("Injecting custom JWK into background.ts...")
252260
backgroundTsPath := filepath.Join(browserExtDir, "src", "background.ts")
253-
if err := injectJWKIntoBackgroundTs(backgroundTsPath, keyData); err != nil {
261+
if err := injectJWKIntoBackgroundTs(backgroundTsPath, jwkData); err != nil {
254262
return "", fmt.Errorf("failed to inject JWK: %w", err)
255263
}
256264
pterm.Success.Println("Custom JWK injected successfully")

pkg/util/crypto.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,50 @@ func ConvertJWKToPEM(jwkJSON string) ([]byte, error) {
8686

8787
return pem.EncodeToMemory(pemBlock), nil
8888
}
89+
90+
// ConvertPEMToJWK converts an Ed25519 PEM private key to JWK format
91+
func ConvertPEMToJWK(pemData string) (string, error) {
92+
// Decode PEM block
93+
block, _ := pem.Decode([]byte(pemData))
94+
if block == nil {
95+
return "", fmt.Errorf("failed to decode PEM block")
96+
}
97+
98+
if block.Type != "PRIVATE KEY" {
99+
return "", fmt.Errorf("invalid PEM type: expected PRIVATE KEY, got %s", block.Type)
100+
}
101+
102+
// Parse PKCS#8 private key
103+
privateKeyInterface, err := x509.ParsePKCS8PrivateKey(block.Bytes)
104+
if err != nil {
105+
return "", fmt.Errorf("failed to parse PKCS#8 private key: %w", err)
106+
}
107+
108+
// Ensure it's an Ed25519 key
109+
privateKey, ok := privateKeyInterface.(ed25519.PrivateKey)
110+
if !ok {
111+
return "", fmt.Errorf("invalid key type: expected Ed25519 private key, got %T", privateKeyInterface)
112+
}
113+
114+
// Extract seed (first 32 bytes of Ed25519 private key)
115+
seed := privateKey.Seed()
116+
117+
// Extract public key (last 32 bytes of Ed25519 private key)
118+
publicKey := privateKey.Public().(ed25519.PublicKey)
119+
120+
// Encode to base64url (without padding)
121+
jwk := jwkKey{
122+
Kty: "OKP",
123+
Crv: "Ed25519",
124+
D: base64.RawURLEncoding.EncodeToString(seed),
125+
X: base64.RawURLEncoding.EncodeToString(publicKey),
126+
}
127+
128+
// Marshal to JSON
129+
jwkJSON, err := json.Marshal(jwk)
130+
if err != nil {
131+
return "", fmt.Errorf("failed to marshal JWK: %w", err)
132+
}
133+
134+
return string(jwkJSON), nil
135+
}

pkg/util/crypto_test.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"crypto/ed25519"
55
"crypto/x509"
66
"encoding/base64"
7+
"encoding/json"
78
"encoding/pem"
89
"testing"
910

@@ -193,3 +194,141 @@ func TestConvertJWKToPEM(t *testing.T) {
193194
})
194195
}
195196
}
197+
198+
func TestConvertPEMToJWK(t *testing.T) {
199+
tests := []struct {
200+
name string
201+
pemData string
202+
wantErr bool
203+
errMsg string
204+
wantKty string
205+
wantCrv string
206+
validateFn func(t *testing.T, jwkJSON string)
207+
}{
208+
{
209+
name: "valid Ed25519 PEM key",
210+
pemData: `-----BEGIN PRIVATE KEY-----
211+
MC4CAQAwBQYDK2VwBCIEIJ+DYvh6SEqVTm50DFtMDoQikTmiCqirVv9mWG9qfSnF
212+
-----END PRIVATE KEY-----`,
213+
wantErr: false,
214+
wantKty: "OKP",
215+
wantCrv: "Ed25519",
216+
validateFn: func(t *testing.T, jwkJSON string) {
217+
// Parse the JWK
218+
var jwk jwkKey
219+
err := json.Unmarshal([]byte(jwkJSON), &jwk)
220+
require.NoError(t, err)
221+
222+
// Verify structure
223+
require.NotEmpty(t, jwk.D, "Private key component 'd' should not be empty")
224+
require.NotEmpty(t, jwk.X, "Public key component 'x' should not be empty")
225+
226+
// Verify the private key can be decoded
227+
privKeyBytes, err := base64.RawURLEncoding.DecodeString(jwk.D)
228+
require.NoError(t, err)
229+
assert.Len(t, privKeyBytes, ed25519.SeedSize, "Private key should be 32 bytes")
230+
231+
// Verify the public key can be decoded
232+
pubKeyBytes, err := base64.RawURLEncoding.DecodeString(jwk.X)
233+
require.NoError(t, err)
234+
assert.Len(t, pubKeyBytes, ed25519.PublicKeySize, "Public key should be 32 bytes")
235+
},
236+
},
237+
{
238+
name: "invalid PEM format",
239+
pemData: "not a pem key",
240+
wantErr: true,
241+
errMsg: "failed to decode PEM block",
242+
},
243+
{
244+
name: "wrong PEM type (public key)",
245+
pemData: `-----BEGIN PUBLIC KEY-----
246+
MCowBQYDK2VwAyEAJrQLj5P/89iXES9+vFgrIy29clF9CC/oPPsw3c5D0bs=
247+
-----END PUBLIC KEY-----`,
248+
wantErr: true,
249+
errMsg: "invalid PEM type",
250+
},
251+
{
252+
name: "invalid PKCS8 data",
253+
pemData: `-----BEGIN PRIVATE KEY-----
254+
aW52YWxpZCBkYXRh
255+
-----END PRIVATE KEY-----`,
256+
wantErr: true,
257+
errMsg: "failed to parse PKCS#8 private key",
258+
},
259+
}
260+
261+
for _, tt := range tests {
262+
t.Run(tt.name, func(t *testing.T) {
263+
jwkJSON, err := ConvertPEMToJWK(tt.pemData)
264+
if tt.wantErr {
265+
require.Error(t, err)
266+
if tt.errMsg != "" {
267+
assert.Contains(t, err.Error(), tt.errMsg)
268+
}
269+
return
270+
}
271+
272+
require.NoError(t, err)
273+
require.NotEmpty(t, jwkJSON)
274+
275+
// Parse and verify JWK structure
276+
var jwk jwkKey
277+
err = json.Unmarshal([]byte(jwkJSON), &jwk)
278+
require.NoError(t, err, "JWK should be valid JSON")
279+
280+
// Verify basic fields
281+
assert.Equal(t, tt.wantKty, jwk.Kty, "Key type should be OKP")
282+
assert.Equal(t, tt.wantCrv, jwk.Crv, "Curve should be Ed25519")
283+
284+
// Run custom validation if provided
285+
if tt.validateFn != nil {
286+
tt.validateFn(t, jwkJSON)
287+
}
288+
})
289+
}
290+
}
291+
292+
func TestPEMToJWKRoundtrip(t *testing.T) {
293+
// Test that converting PEM -> JWK -> PEM produces equivalent keys
294+
originalPEM := `-----BEGIN PRIVATE KEY-----
295+
MC4CAQAwBQYDK2VwBCIEIJ+DYvh6SEqVTm50DFtMDoQikTmiCqirVv9mWG9qfSnF
296+
-----END PRIVATE KEY-----`
297+
298+
// Convert PEM to JWK
299+
jwkJSON, err := ConvertPEMToJWK(originalPEM)
300+
require.NoError(t, err)
301+
require.NotEmpty(t, jwkJSON)
302+
303+
// Convert JWK back to PEM
304+
pemData, err := ConvertJWKToPEM(jwkJSON)
305+
require.NoError(t, err)
306+
require.NotEmpty(t, pemData)
307+
308+
// Parse both keys and verify they're the same
309+
block1, _ := pem.Decode([]byte(originalPEM))
310+
require.NotNil(t, block1)
311+
key1, err := x509.ParsePKCS8PrivateKey(block1.Bytes)
312+
require.NoError(t, err)
313+
ed25519Key1 := key1.(ed25519.PrivateKey)
314+
315+
block2, _ := pem.Decode(pemData)
316+
require.NotNil(t, block2)
317+
key2, err := x509.ParsePKCS8PrivateKey(block2.Bytes)
318+
require.NoError(t, err)
319+
ed25519Key2 := key2.(ed25519.PrivateKey)
320+
321+
// Compare private keys
322+
assert.Equal(t, ed25519Key1, ed25519Key2, "Keys should be identical after roundtrip")
323+
324+
// Verify signatures match
325+
message := []byte("test message for signature verification")
326+
sig1 := ed25519.Sign(ed25519Key1, message)
327+
sig2 := ed25519.Sign(ed25519Key2, message)
328+
329+
pubKey1 := ed25519Key1.Public().(ed25519.PublicKey)
330+
pubKey2 := ed25519Key2.Public().(ed25519.PublicKey)
331+
332+
assert.True(t, ed25519.Verify(pubKey1, message, sig2), "Key1 should verify signature from Key2")
333+
assert.True(t, ed25519.Verify(pubKey2, message, sig1), "Key2 should verify signature from Key1")
334+
}

0 commit comments

Comments
 (0)