Skip to content

Commit 2e5da41

Browse files
committed
Add explicit unmarshaller for signing and key agreement public keys
1 parent 398d67e commit 2e5da41

2 files changed

Lines changed: 95 additions & 45 deletions

File tree

cert/pem.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ func MarshalSigningPublicKeyToPEM(curve Curve, b []byte) []byte {
148148
}
149149
}
150150

151+
// UnmarshalPublicKeyFromPEM will try to unmarshal the first pem block in a byte array, returning any non
152+
// consumed data or an error on failure. Only key-agreement (ECDH) public key banners are accepted.
153+
// Use UnmarshalSigningPublicKeyFromPEM for Ed25519/ECDSA banners.
151154
func UnmarshalPublicKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {
152155
k, r := pem.Decode(b)
153156
if k == nil {
@@ -156,10 +159,10 @@ func UnmarshalPublicKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {
156159
var expectedLen int
157160
var curve Curve
158161
switch k.Type {
159-
case X25519PublicKeyBanner, Ed25519PublicKeyBanner:
162+
case X25519PublicKeyBanner:
160163
expectedLen = 32
161164
curve = Curve_CURVE25519
162-
case P256PublicKeyBanner, ECDSAP256PublicKeyBanner:
165+
case P256PublicKeyBanner:
163166
// Uncompressed
164167
expectedLen = 65
165168
curve = Curve_P256
@@ -172,6 +175,33 @@ func UnmarshalPublicKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {
172175
return k.Bytes, r, curve, nil
173176
}
174177

178+
// UnmarshalSigningPublicKeyFromPEM will try to unmarshal the first pem block in a byte array, returning any non
179+
// consumed data or an error on failure. Only Ed25519/ECDSA public key banners are accepted.
180+
// Use UnmarshalPublicKeyFromPEM for X25519/P256 (ECDH) banners.
181+
func UnmarshalSigningPublicKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {
182+
k, r := pem.Decode(b)
183+
if k == nil {
184+
return nil, r, 0, fmt.Errorf("input did not contain a valid PEM encoded block")
185+
}
186+
var expectedLen int
187+
var curve Curve
188+
switch k.Type {
189+
case Ed25519PublicKeyBanner:
190+
expectedLen = 32
191+
curve = Curve_CURVE25519
192+
case ECDSAP256PublicKeyBanner:
193+
// Uncompressed
194+
expectedLen = 65
195+
curve = Curve_P256
196+
default:
197+
return nil, r, 0, fmt.Errorf("bytes did not contain a proper Ed25519/ECDSA public key banner")
198+
}
199+
if len(k.Bytes) != expectedLen {
200+
return nil, r, 0, fmt.Errorf("key was not %d bytes, is invalid %s public key", expectedLen, curve)
201+
}
202+
return k.Bytes, r, curve, nil
203+
}
204+
175205
func MarshalPrivateKeyToPEM(curve Curve, b []byte) []byte {
176206
switch curve {
177207
case Curve_CURVE25519:

cert/pem_test.go

Lines changed: 63 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -255,129 +255,149 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
255255
func TestUnmarshalPublicKeyFromPEM(t *testing.T) {
256256
t.Parallel()
257257
pubKey := []byte(`# A good key
258-
-----BEGIN NEBULA ED25519 PUBLIC KEY-----
258+
-----BEGIN NEBULA X25519 PUBLIC KEY-----
259259
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
260-
-----END NEBULA ED25519 PUBLIC KEY-----
260+
-----END NEBULA X25519 PUBLIC KEY-----
261+
`)
262+
pubP256Key := []byte(`# A good key
263+
-----BEGIN NEBULA P256 PUBLIC KEY-----
264+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
265+
AAAAAAAAAAAAAAAAAAAAAAA=
266+
-----END NEBULA P256 PUBLIC KEY-----
267+
`)
268+
signingKey := []byte(`# A signing key has the wrong scope for this function
269+
-----BEGIN NEBULA ECDSA P256 PUBLIC KEY-----
270+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
271+
AAAAAAAAAAAAAAAAAAAAAAA=
272+
-----END NEBULA ECDSA P256 PUBLIC KEY-----
261273
`)
262274
shortKey := []byte(`# A short key
263-
-----BEGIN NEBULA ED25519 PUBLIC KEY-----
275+
-----BEGIN NEBULA X25519 PUBLIC KEY-----
264276
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
265-
-----END NEBULA ED25519 PUBLIC KEY-----
277+
-----END NEBULA X25519 PUBLIC KEY-----
266278
`)
267279
invalidBanner := []byte(`# Invalid banner
268280
-----BEGIN NOT A NEBULA PUBLIC KEY-----
269281
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
270282
-----END NOT A NEBULA PUBLIC KEY-----
271283
`)
272284
invalidPem := []byte(`# Not a valid PEM format
273-
-BEGIN NEBULA ED25519 PUBLIC KEY-----
285+
-BEGIN NEBULA X25519 PUBLIC KEY-----
274286
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
275-
-END NEBULA ED25519 PUBLIC KEY-----`)
287+
-END NEBULA X25519 PUBLIC KEY-----`)
276288

277-
keyBundle := appendByteSlices(pubKey, shortKey, invalidBanner, invalidPem)
289+
keyBundle := appendByteSlices(pubKey, pubP256Key, signingKey, shortKey, invalidBanner, invalidPem)
278290

279-
// Success test case
291+
// X25519 key
280292
k, rest, curve, err := UnmarshalPublicKeyFromPEM(keyBundle)
281293
assert.Len(t, k, 32)
294+
require.NoError(t, err)
295+
assert.Equal(t, rest, appendByteSlices(pubP256Key, signingKey, shortKey, invalidBanner, invalidPem))
282296
assert.Equal(t, Curve_CURVE25519, curve)
297+
298+
// P256 key
299+
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
300+
assert.Len(t, k, 65)
283301
require.NoError(t, err)
302+
assert.Equal(t, rest, appendByteSlices(signingKey, shortKey, invalidBanner, invalidPem))
303+
assert.Equal(t, Curve_P256, curve)
304+
305+
// Reject a signing public key (Ed25519/ECDSA banner)
306+
k, rest, _, err = UnmarshalPublicKeyFromPEM(rest)
307+
assert.Nil(t, k)
284308
assert.Equal(t, rest, appendByteSlices(shortKey, invalidBanner, invalidPem))
309+
require.EqualError(t, err, "bytes did not contain a proper public key banner")
285310

286311
// Fail due to short key
287-
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
312+
k, rest, _, err = UnmarshalPublicKeyFromPEM(rest)
288313
assert.Nil(t, k)
289-
assert.Equal(t, Curve_CURVE25519, curve)
290314
assert.Equal(t, rest, appendByteSlices(invalidBanner, invalidPem))
291315
require.EqualError(t, err, "key was not 32 bytes, is invalid CURVE25519 public key")
292316

293317
// Fail due to invalid banner
294-
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
318+
k, rest, _, err = UnmarshalPublicKeyFromPEM(rest)
295319
assert.Nil(t, k)
296-
assert.Equal(t, Curve_CURVE25519, curve)
297320
require.EqualError(t, err, "bytes did not contain a proper public key banner")
298321
assert.Equal(t, rest, invalidPem)
299322

300323
// Fail due to invalid PEM format, because
301324
// it's missing the requisite pre-encapsulation boundary.
302-
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
325+
k, rest, _, err = UnmarshalPublicKeyFromPEM(rest)
303326
assert.Nil(t, k)
304-
assert.Equal(t, Curve_CURVE25519, curve)
305327
assert.Equal(t, rest, invalidPem)
306328
require.EqualError(t, err, "input did not contain a valid PEM encoded block")
307329
}
308330

309-
func TestUnmarshalX25519PublicKey(t *testing.T) {
331+
func TestUnmarshalSigningPublicKeyFromPEM(t *testing.T) {
310332
t.Parallel()
311333
pubKey := []byte(`# A good key
312-
-----BEGIN NEBULA X25519 PUBLIC KEY-----
334+
-----BEGIN NEBULA ED25519 PUBLIC KEY-----
313335
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
314-
-----END NEBULA X25519 PUBLIC KEY-----
336+
-----END NEBULA ED25519 PUBLIC KEY-----
315337
`)
316338
pubP256Key := []byte(`# A good key
317-
-----BEGIN NEBULA P256 PUBLIC KEY-----
318-
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
319-
AAAAAAAAAAAAAAAAAAAAAAA=
320-
-----END NEBULA P256 PUBLIC KEY-----
321-
`)
322-
oldPubP256Key := []byte(`# A good key
323339
-----BEGIN NEBULA ECDSA P256 PUBLIC KEY-----
324340
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
325341
AAAAAAAAAAAAAAAAAAAAAAA=
326342
-----END NEBULA ECDSA P256 PUBLIC KEY-----
327343
`)
328-
shortKey := []byte(`# A short key
344+
ecdhKey := []byte(`# A key-agreement key has the wrong scope for this function
329345
-----BEGIN NEBULA X25519 PUBLIC KEY-----
330-
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
346+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
331347
-----END NEBULA X25519 PUBLIC KEY-----
348+
`)
349+
shortKey := []byte(`# A short key
350+
-----BEGIN NEBULA ED25519 PUBLIC KEY-----
351+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
352+
-----END NEBULA ED25519 PUBLIC KEY-----
332353
`)
333354
invalidBanner := []byte(`# Invalid banner
334355
-----BEGIN NOT A NEBULA PUBLIC KEY-----
335356
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
336357
-----END NOT A NEBULA PUBLIC KEY-----
337358
`)
338359
invalidPem := []byte(`# Not a valid PEM format
339-
-BEGIN NEBULA X25519 PUBLIC KEY-----
360+
-BEGIN NEBULA ED25519 PUBLIC KEY-----
340361
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
341-
-END NEBULA X25519 PUBLIC KEY-----`)
362+
-END NEBULA ED25519 PUBLIC KEY-----`)
342363

343-
keyBundle := appendByteSlices(pubKey, pubP256Key, oldPubP256Key, shortKey, invalidBanner, invalidPem)
364+
keyBundle := appendByteSlices(pubKey, pubP256Key, ecdhKey, shortKey, invalidBanner, invalidPem)
344365

345-
// Success test case
346-
k, rest, curve, err := UnmarshalPublicKeyFromPEM(keyBundle)
366+
// Ed25519 key
367+
k, rest, curve, err := UnmarshalSigningPublicKeyFromPEM(keyBundle)
347368
assert.Len(t, k, 32)
348369
require.NoError(t, err)
349-
assert.Equal(t, rest, appendByteSlices(pubP256Key, oldPubP256Key, shortKey, invalidBanner, invalidPem))
370+
assert.Equal(t, rest, appendByteSlices(pubP256Key, ecdhKey, shortKey, invalidBanner, invalidPem))
350371
assert.Equal(t, Curve_CURVE25519, curve)
351372

352-
// Success test case
353-
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
373+
// ECDSA P256 key
374+
k, rest, curve, err = UnmarshalSigningPublicKeyFromPEM(rest)
354375
assert.Len(t, k, 65)
355376
require.NoError(t, err)
356-
assert.Equal(t, rest, appendByteSlices(oldPubP256Key, shortKey, invalidBanner, invalidPem))
377+
assert.Equal(t, rest, appendByteSlices(ecdhKey, shortKey, invalidBanner, invalidPem))
357378
assert.Equal(t, Curve_P256, curve)
358379

359-
// Success test case
360-
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
361-
assert.Len(t, k, 65)
362-
require.NoError(t, err)
380+
// Reject a key-agreement public key (X25519/P256 banner)
381+
k, rest, _, err = UnmarshalSigningPublicKeyFromPEM(rest)
382+
assert.Nil(t, k)
363383
assert.Equal(t, rest, appendByteSlices(shortKey, invalidBanner, invalidPem))
364-
assert.Equal(t, Curve_P256, curve)
384+
require.EqualError(t, err, "bytes did not contain a proper Ed25519/ECDSA public key banner")
365385

366386
// Fail due to short key
367-
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
387+
k, rest, _, err = UnmarshalSigningPublicKeyFromPEM(rest)
368388
assert.Nil(t, k)
369389
assert.Equal(t, rest, appendByteSlices(invalidBanner, invalidPem))
370390
require.EqualError(t, err, "key was not 32 bytes, is invalid CURVE25519 public key")
371391

372392
// Fail due to invalid banner
373-
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
393+
k, rest, _, err = UnmarshalSigningPublicKeyFromPEM(rest)
374394
assert.Nil(t, k)
375-
require.EqualError(t, err, "bytes did not contain a proper public key banner")
395+
require.EqualError(t, err, "bytes did not contain a proper Ed25519/ECDSA public key banner")
376396
assert.Equal(t, rest, invalidPem)
377397

378398
// Fail due to invalid PEM format, because
379399
// it's missing the requisite pre-encapsulation boundary.
380-
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
400+
k, rest, _, err = UnmarshalSigningPublicKeyFromPEM(rest)
381401
assert.Nil(t, k)
382402
assert.Equal(t, rest, invalidPem)
383403
require.EqualError(t, err, "input did not contain a valid PEM encoded block")

0 commit comments

Comments
 (0)