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