Cross-platform RSA & EC keypair generation, signing and verification - suitable for JWT, JOSE, and asymmetric cryptography.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 

174 lignes
4.2 KiB

package keypairs
import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/sha256"
"crypto/subtle"
"encoding/base64"
"errors"
"fmt"
"log"
"math/big"
"time"
)
// VerifyClaims will check the signature of a parsed JWT
func VerifyClaims(pubkey PublicKey, jws *JWS) (errs []error) {
kid, _ := jws.Header["kid"].(string)
jwkmap, hasJWK := jws.Header["jwk"].(Object)
//var jwk JWK = nil
seed, _ := jws.Header["_seed"].(int64)
seedf64, _ := jws.Header["_seed"].(float64)
kty, _ := jws.Header["_kty"].(string)
if 0 == seed {
seed = int64(seedf64)
}
var pub PublicKey = nil
if hasJWK {
pub, errs = selfsignCheck(jwkmap, errs)
} else {
opts := &keyOptions{mockSeed: seed, KeyType: kty}
pub, errs = pubkeyCheck(pubkey, kid, opts, errs)
}
jti, _ := jws.Claims["jti"].(string)
expf64, _ := jws.Claims["exp"].(float64)
exp := int64(expf64)
if 0 == exp {
if "" == jti {
err := errors.New("one of 'jti' or 'exp' must exist for token expiry")
errs = append(errs, err)
}
} else {
if time.Now().Unix() > exp {
err := fmt.Errorf("token expired at %d (%s)", exp, time.Unix(exp, 0))
errs = append(errs, err)
}
}
signable := fmt.Sprintf("%s.%s", jws.Protected, jws.Payload)
hash := sha256.Sum256([]byte(signable))
sig, err := base64.RawURLEncoding.DecodeString(jws.Signature)
if nil != err {
err := fmt.Errorf("could not decode signature: %w", err)
errs = append(errs, err)
return errs
}
//log.Printf("\n(Verify)\nSignable: %s", signable)
//log.Printf("Hash: %s", hash)
//log.Printf("Sig: %s", jws.Signature)
if nil == pub {
err := fmt.Errorf("token signature could not be verified")
errs = append(errs, err)
} else if !Verify(pub, hash[:], sig) {
err := fmt.Errorf("token signature is not valid")
errs = append(errs, err)
}
return errs
}
func selfsignCheck(jwkmap Object, errs []error) (PublicKey, []error) {
var pub PublicKeyDeprecated = nil
log.Println("Security TODO: did not check jws.Claims[\"sub\"] against 'jwk'")
log.Println("Security TODO: did not check jws.Claims[\"iss\"]")
kty := jwkmap["kty"]
var err error
if "RSA" == kty {
e, _ := jwkmap["e"].(string)
n, _ := jwkmap["n"].(string)
k, _ := (&RSAJWK{
Exp: e,
N: n,
}).marshalJWK()
pub, err = ParseJWKPublicKey(k)
if nil != err {
return nil, append(errs, err)
}
} else {
crv, _ := jwkmap["crv"].(string)
x, _ := jwkmap["x"].(string)
y, _ := jwkmap["y"].(string)
k, _ := (&ECJWK{
Curve: crv,
X: x,
Y: y,
}).marshalJWK()
pub, err = ParseJWKPublicKey(k)
if nil != err {
return nil, append(errs, err)
}
}
return pub.Key(), errs
}
func pubkeyCheck(pubkey PublicKey, kid string, opts *keyOptions, errs []error) (PublicKey, []error) {
var pub PublicKey = nil
if "" == kid {
err := errors.New("token should have 'kid' or 'jwk' in header to identify the public key")
errs = append(errs, err)
}
if nil == pubkey {
if allowMocking {
if 0 == opts.mockSeed {
err := errors.New("the debug API requires '_seed' to accompany 'kid'")
errs = append(errs, err)
}
if "" == opts.KeyType {
err := errors.New("the debug API requires '_kty' to accompany '_seed'")
errs = append(errs, err)
}
if 0 == opts.mockSeed || "" == opts.KeyType {
return nil, errs
}
privkey := newPrivateKey(opts)
pub = privkey.Public().(PublicKey)
return pub, errs
}
err := errors.New("no matching public key")
errs = append(errs, err)
} else {
pub = pubkey
}
if nil != pub && "" != kid {
if 1 != subtle.ConstantTimeCompare([]byte(kid), []byte(Thumbprint(pub))) {
err := errors.New("'kid' does not match the public key thumbprint")
errs = append(errs, err)
}
}
return pub, errs
}
// Verify will check the signature of a hash
func Verify(pubkey PublicKey, hash []byte, sig []byte) bool {
switch pub := pubkey.(type) {
case *rsa.PublicKey:
//log.Printf("RSA VERIFY")
// TODO Size(key) to detect key size ?
//alg := "SHA256"
// TODO: this hasn't been tested yet
if err := rsa.VerifyPKCS1v15(pub, crypto.SHA256, hash, sig); nil != err {
return false
}
return true
case *ecdsa.PublicKey:
r := &big.Int{}
r.SetBytes(sig[0:32])
s := &big.Int{}
s.SetBytes(sig[32:])
return ecdsa.Verify(pub, hash, r, s)
default:
panic("impossible condition: non-rsa/non-ecdsa key")
//return false
}
}