1package webauthncose
2
3import (
4	"crypto"
5	"crypto/ecdsa"
6	"crypto/elliptic"
7	"crypto/rsa"
8	"crypto/x509"
9	"encoding/asn1"
10	"encoding/pem"
11	"fmt"
12	"hash"
13	"math/big"
14
15	"golang.org/x/crypto/ed25519"
16
17	"github.com/duo-labs/webauthn/protocol/webauthncbor"
18)
19
20// PublicKeyData The public key portion of a Relying Party-specific credential key pair, generated
21// by an authenticator and returned to a Relying Party at registration time. We unpack this object
22// using fxamacker's cbor library ("github.com/fxamacker/cbor/v2") which is why there are cbor tags
23// included. The tag field values correspond to the IANA COSE keys that give their respective
24// values.
25// See §6.4.1.1 https://www.w3.org/TR/webauthn/#sctn-encoded-credPubKey-examples for examples of this
26// COSE data.
27type PublicKeyData struct {
28	// Decode the results to int by default.
29	_struct bool `cbor:",keyasint" json:"public_key"`
30	// The type of key created. Should be OKP, EC2, or RSA.
31	KeyType int64 `cbor:"1,keyasint" json:"kty"`
32	// A COSEAlgorithmIdentifier for the algorithm used to derive the key signature.
33	Algorithm int64 `cbor:"3,keyasint" json:"alg"`
34}
35type EC2PublicKeyData struct {
36	PublicKeyData
37	// If the key type is EC2, the curve on which we derive the signature from.
38	Curve int64 `cbor:"-1,keyasint,omitempty" json:"crv"`
39	// A byte string 32 bytes in length that holds the x coordinate of the key.
40	XCoord []byte `cbor:"-2,keyasint,omitempty" json:"x"`
41	// A byte string 32 bytes in length that holds the y coordinate of the key.
42	YCoord []byte `cbor:"-3,keyasint,omitempty" json:"y"`
43}
44
45type RSAPublicKeyData struct {
46	PublicKeyData
47	// Represents the modulus parameter for the RSA algorithm
48	Modulus []byte `cbor:"-1,keyasint,omitempty" json:"n"`
49	// Represents the exponent parameter for the RSA algorithm
50	Exponent []byte `cbor:"-2,keyasint,omitempty" json:"e"`
51}
52
53type OKPPublicKeyData struct {
54	PublicKeyData
55	Curve int64
56	// A byte string that holds the x coordinate of the key.
57	XCoord []byte `cbor:"-2,keyasint,omitempty" json:"x"`
58}
59
60// Verify Octet Key Pair (OKP) Public Key Signature
61func (k *OKPPublicKeyData) Verify(data []byte, sig []byte) (bool, error) {
62	var key ed25519.PublicKey = make([]byte, ed25519.PublicKeySize)
63	copy(key, k.XCoord)
64	return ed25519.Verify(key, data, sig), nil
65}
66
67// Verify Elliptic Curce Public Key Signature
68func (k *EC2PublicKeyData) Verify(data []byte, sig []byte) (bool, error) {
69	var curve elliptic.Curve
70	switch COSEAlgorithmIdentifier(k.Algorithm) {
71	case AlgES512: // IANA COSE code for ECDSA w/ SHA-512
72		curve = elliptic.P521()
73	case AlgES384: // IANA COSE code for ECDSA w/ SHA-384
74		curve = elliptic.P384()
75	case AlgES256: // IANA COSE code for ECDSA w/ SHA-256
76		curve = elliptic.P256()
77	default:
78		return false, ErrUnsupportedAlgorithm
79	}
80
81	pubkey := &ecdsa.PublicKey{
82		Curve: curve,
83		X:     big.NewInt(0).SetBytes(k.XCoord),
84		Y:     big.NewInt(0).SetBytes(k.YCoord),
85	}
86
87	type ECDSASignature struct {
88		R, S *big.Int
89	}
90
91	e := &ECDSASignature{}
92	f := HasherFromCOSEAlg(COSEAlgorithmIdentifier(k.PublicKeyData.Algorithm))
93	h := f()
94	h.Write(data)
95	_, err := asn1.Unmarshal(sig, e)
96	if err != nil {
97		return false, ErrSigNotProvidedOrInvalid
98	}
99	return ecdsa.Verify(pubkey, h.Sum(nil), e.R, e.S), nil
100}
101
102// Verify RSA Public Key Signature
103func (k *RSAPublicKeyData) Verify(data []byte, sig []byte) (bool, error) {
104	pubkey := &rsa.PublicKey{
105		N: big.NewInt(0).SetBytes(k.Modulus),
106		E: int(uint(k.Exponent[2]) | uint(k.Exponent[1])<<8 | uint(k.Exponent[0])<<16),
107	}
108
109	f := HasherFromCOSEAlg(COSEAlgorithmIdentifier(k.PublicKeyData.Algorithm))
110	h := f()
111	h.Write(data)
112
113	var hash crypto.Hash
114	switch COSEAlgorithmIdentifier(k.PublicKeyData.Algorithm) {
115	case AlgRS1:
116		hash = crypto.SHA1
117	case AlgPS256, AlgRS256:
118		hash = crypto.SHA256
119	case AlgPS384, AlgRS384:
120		hash = crypto.SHA384
121	case AlgPS512, AlgRS512:
122		hash = crypto.SHA512
123	default:
124		return false, ErrUnsupportedAlgorithm
125	}
126	switch COSEAlgorithmIdentifier(k.PublicKeyData.Algorithm) {
127	case AlgPS256, AlgPS384, AlgPS512:
128		err := rsa.VerifyPSS(pubkey, hash, h.Sum(nil), sig, nil)
129		return err == nil, err
130
131	case AlgRS1, AlgRS256, AlgRS384, AlgRS512:
132		err := rsa.VerifyPKCS1v15(pubkey, hash, h.Sum(nil), sig)
133		return err == nil, err
134	default:
135		return false, ErrUnsupportedAlgorithm
136	}
137}
138
139// Return which signature algorithm is being used from the COSE Key
140func SigAlgFromCOSEAlg(coseAlg COSEAlgorithmIdentifier) SignatureAlgorithm {
141	for _, details := range SignatureAlgorithmDetails {
142		if details.coseAlg == coseAlg {
143			return details.algo
144		}
145	}
146	return UnknownSignatureAlgorithm
147}
148
149// Return the Hashing interface to be used for a given COSE Algorithm
150func HasherFromCOSEAlg(coseAlg COSEAlgorithmIdentifier) func() hash.Hash {
151	for _, details := range SignatureAlgorithmDetails {
152		if details.coseAlg == coseAlg {
153			return details.hasher
154		}
155	}
156	// default to SHA256?  Why not.
157	return crypto.SHA256.New
158}
159
160// Figure out what kind of COSE material was provided and create the data for the new key
161func ParsePublicKey(keyBytes []byte) (interface{}, error) {
162	pk := PublicKeyData{}
163	webauthncbor.Unmarshal(keyBytes, &pk)
164	switch COSEKeyType(pk.KeyType) {
165	case OctetKey:
166		var o OKPPublicKeyData
167		webauthncbor.Unmarshal(keyBytes, &o)
168		o.PublicKeyData = pk
169		return o, nil
170	case EllipticKey:
171		var e EC2PublicKeyData
172		webauthncbor.Unmarshal(keyBytes, &e)
173		e.PublicKeyData = pk
174		return e, nil
175	case RSAKey:
176		var r RSAPublicKeyData
177		webauthncbor.Unmarshal(keyBytes, &r)
178		r.PublicKeyData = pk
179		return r, nil
180	default:
181		return nil, ErrUnsupportedKey
182	}
183}
184
185// ParseFIDOPublicKey is only used when the appID extension is configured by the assertion response.
186func ParseFIDOPublicKey(keyBytes []byte) (EC2PublicKeyData, error) {
187	x, y := elliptic.Unmarshal(elliptic.P256(), keyBytes)
188
189	return EC2PublicKeyData{
190		PublicKeyData: PublicKeyData{
191			Algorithm: int64(AlgES256),
192		},
193		XCoord: x.Bytes(),
194		YCoord: y.Bytes(),
195	}, nil
196}
197
198// COSEAlgorithmIdentifier From §5.10.5. A number identifying a cryptographic algorithm. The algorithm
199// identifiers SHOULD be values registered in the IANA COSE Algorithms registry
200// [https://www.w3.org/TR/webauthn/#biblio-iana-cose-algs-reg], for instance, -7 for "ES256"
201//  and -257 for "RS256".
202type COSEAlgorithmIdentifier int
203
204const (
205	// AlgES256 ECDSA with SHA-256
206	AlgES256 COSEAlgorithmIdentifier = -7
207	// AlgES384 ECDSA with SHA-384
208	AlgES384 COSEAlgorithmIdentifier = -35
209	// AlgES512 ECDSA with SHA-512
210	AlgES512 COSEAlgorithmIdentifier = -36
211	// AlgRS1 RSASSA-PKCS1-v1_5 with SHA-1
212	AlgRS1 COSEAlgorithmIdentifier = -65535
213	// AlgRS256 RSASSA-PKCS1-v1_5 with SHA-256
214	AlgRS256 COSEAlgorithmIdentifier = -257
215	// AlgRS384 RSASSA-PKCS1-v1_5 with SHA-384
216	AlgRS384 COSEAlgorithmIdentifier = -258
217	// AlgRS512 RSASSA-PKCS1-v1_5 with SHA-512
218	AlgRS512 COSEAlgorithmIdentifier = -259
219	// AlgPS256 RSASSA-PSS with SHA-256
220	AlgPS256 COSEAlgorithmIdentifier = -37
221	// AlgPS384 RSASSA-PSS with SHA-384
222	AlgPS384 COSEAlgorithmIdentifier = -38
223	// AlgPS512 RSASSA-PSS with SHA-512
224	AlgPS512 COSEAlgorithmIdentifier = -39
225	// AlgEdDSA EdDSA
226	AlgEdDSA COSEAlgorithmIdentifier = -8
227)
228
229// The Key Type derived from the IANA COSE AuthData
230type COSEKeyType int
231
232const (
233	// OctetKey is an Octet Key
234	OctetKey COSEKeyType = 1
235	// EllipticKey is an Elliptic Curve Public Key
236	EllipticKey COSEKeyType = 2
237	// RSAKey is an RSA Public Key
238	RSAKey COSEKeyType = 3
239)
240
241func VerifySignature(key interface{}, data []byte, sig []byte) (bool, error) {
242
243	switch key.(type) {
244	case OKPPublicKeyData:
245		o := key.(OKPPublicKeyData)
246		return o.Verify(data, sig)
247	case EC2PublicKeyData:
248		e := key.(EC2PublicKeyData)
249		return e.Verify(data, sig)
250	case RSAPublicKeyData:
251		r := key.(RSAPublicKeyData)
252		return r.Verify(data, sig)
253	default:
254		return false, ErrUnsupportedKey
255	}
256}
257
258func DisplayPublicKey(cpk []byte) string {
259	parsedKey, err := ParsePublicKey(cpk)
260	if err != nil {
261		return "Cannot display key"
262	}
263	switch parsedKey.(type) {
264	case RSAPublicKeyData:
265		pKey := parsedKey.(RSAPublicKeyData)
266		rKey := &rsa.PublicKey{
267			N: big.NewInt(0).SetBytes(pKey.Modulus),
268			E: int(uint(pKey.Exponent[2]) | uint(pKey.Exponent[1])<<8 | uint(pKey.Exponent[0])<<16),
269		}
270		data, err := x509.MarshalPKIXPublicKey(rKey)
271		if err != nil {
272			return "Cannot display key"
273		}
274		pemBytes := pem.EncodeToMemory(&pem.Block{
275			Type:  "RSA PUBLIC KEY",
276			Bytes: data,
277		})
278		return fmt.Sprintf("%s", pemBytes)
279	case EC2PublicKeyData:
280		pKey := parsedKey.(EC2PublicKeyData)
281		var curve elliptic.Curve
282		switch COSEAlgorithmIdentifier(pKey.Algorithm) {
283		case AlgES256:
284			curve = elliptic.P256()
285		case AlgES384:
286			curve = elliptic.P384()
287		case AlgES512:
288			curve = elliptic.P521()
289		default:
290			return "Cannot display key"
291		}
292		eKey := &ecdsa.PublicKey{
293			Curve: curve,
294			X:     big.NewInt(0).SetBytes(pKey.XCoord),
295			Y:     big.NewInt(0).SetBytes(pKey.YCoord),
296		}
297		data, err := x509.MarshalPKIXPublicKey(eKey)
298		if err != nil {
299			return "Cannot display key"
300		}
301		pemBytes := pem.EncodeToMemory(&pem.Block{
302			Type:  "PUBLIC KEY",
303			Bytes: data,
304		})
305		return fmt.Sprintf("%s", pemBytes)
306	case OKPPublicKeyData:
307		pKey := parsedKey.(OKPPublicKeyData)
308		if len(pKey.XCoord) != ed25519.PublicKeySize {
309			return "Cannot display key"
310		}
311		var oKey ed25519.PublicKey = make([]byte, ed25519.PublicKeySize)
312		copy(oKey, pKey.XCoord)
313		data, err := marshalEd25519PublicKey(oKey)
314		if err != nil {
315			return "Cannot display key"
316		}
317		pemBytes := pem.EncodeToMemory(&pem.Block{
318			Type:  "PUBLIC KEY",
319			Bytes: data,
320		})
321		return fmt.Sprintf("%s", pemBytes)
322
323	default:
324		return "Cannot display key of this type"
325	}
326}
327
328// Algorithm enumerations used for
329type SignatureAlgorithm int
330
331const (
332	UnknownSignatureAlgorithm SignatureAlgorithm = iota
333	MD2WithRSA
334	MD5WithRSA
335	SHA1WithRSA
336	SHA256WithRSA
337	SHA384WithRSA
338	SHA512WithRSA
339	DSAWithSHA1
340	DSAWithSHA256
341	ECDSAWithSHA1
342	ECDSAWithSHA256
343	ECDSAWithSHA384
344	ECDSAWithSHA512
345	SHA256WithRSAPSS
346	SHA384WithRSAPSS
347	SHA512WithRSAPSS
348)
349
350var SignatureAlgorithmDetails = []struct {
351	algo    SignatureAlgorithm
352	coseAlg COSEAlgorithmIdentifier
353	name    string
354	hasher  func() hash.Hash
355}{
356	{SHA1WithRSA, AlgRS1, "SHA1-RSA", crypto.SHA1.New},
357	{SHA256WithRSA, AlgRS256, "SHA256-RSA", crypto.SHA256.New},
358	{SHA384WithRSA, AlgRS384, "SHA384-RSA", crypto.SHA384.New},
359	{SHA512WithRSA, AlgRS512, "SHA512-RSA", crypto.SHA512.New},
360	{SHA256WithRSAPSS, AlgPS256, "SHA256-RSAPSS", crypto.SHA256.New},
361	{SHA384WithRSAPSS, AlgPS384, "SHA384-RSAPSS", crypto.SHA384.New},
362	{SHA512WithRSAPSS, AlgPS512, "SHA512-RSAPSS", crypto.SHA512.New},
363	{ECDSAWithSHA256, AlgES256, "ECDSA-SHA256", crypto.SHA256.New},
364	{ECDSAWithSHA384, AlgES384, "ECDSA-SHA384", crypto.SHA384.New},
365	{ECDSAWithSHA512, AlgES512, "ECDSA-SHA512", crypto.SHA512.New},
366	{UnknownSignatureAlgorithm, AlgEdDSA, "EdDSA", crypto.SHA512.New},
367}
368
369type Error struct {
370	// Short name for the type of error that has occurred
371	Type string `json:"type"`
372	// Additional details about the error
373	Details string `json:"error"`
374	// Information to help debug the error
375	DevInfo string `json:"debug"`
376}
377
378var (
379	ErrUnsupportedKey = &Error{
380		Type:    "invalid_key_type",
381		Details: "Unsupported Public Key Type",
382	}
383	ErrUnsupportedAlgorithm = &Error{
384		Type:    "unsupported_key_algorithm",
385		Details: "Unsupported public key algorithm",
386	}
387	ErrSigNotProvidedOrInvalid = &Error{
388		Type:    "signature_not_provided_or_invalid",
389		Details: "Signature invalid or not provided",
390	}
391)
392
393func (err *Error) Error() string {
394	return err.Details
395}
396
397func (passedError *Error) WithDetails(details string) *Error {
398	err := *passedError
399	err.Details = details
400	return &err
401}
402