Adds JWS encode and decode
It also adds interfaces Signer and HS256 and RS256 implementation.
This commit is contained in:
133
jwt/jws.go
Normal file
133
jwt/jws.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// EncodeJWS encode a JSON Object with the serialized JWS format as
|
||||
// specified in RFC 7515
|
||||
func EncodeJWS(j *JOSE, v interface{}, s Signer) ([]byte, error) {
|
||||
payload, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
j.Algorithm = s.Algorithm()
|
||||
|
||||
header, err := j.EncodeJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Allocate a buffer long enough for all payload
|
||||
res := make([]byte, Base64EncodedBufferLen(len(header))+Base64EncodedBufferLen(len(payload))+2)
|
||||
base64.URLEncoding.Encode(res, header)
|
||||
lengthHeader := Base64EncodedStrippedLen(len(res))
|
||||
res[lengthHeader] = '.'
|
||||
base64.URLEncoding.Encode(res[lengthHeader+1:], payload)
|
||||
fullPayloadLength := Base64EncodedBufferLen(len(payload)) + 1 + lengthHeader
|
||||
res[fullPayloadLength] = '.'
|
||||
|
||||
if s == nil {
|
||||
// unprotected jws, not signing it
|
||||
return res[:fullPayloadLength+1], nil
|
||||
}
|
||||
signature, err := s.Sign(res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signedRes := make([]byte, fullPayloadLength+1+Base64EncodedBufferLen(len(signature)))
|
||||
copy(signedRes, res[:fullPayloadLength+1])
|
||||
base64.URLEncoding.Encode(signedRes[fullPayloadLength+1:], signature)
|
||||
return signedRes[:fullPayloadLength+1+Base64EncodedStrippedLen(len(signature))], nil
|
||||
}
|
||||
|
||||
// isBase64URLEncoding returns if the byte is a character used for
|
||||
// base64 URL Encoding
|
||||
func isBase64URLEncoding(b byte) bool {
|
||||
if b == '-' || b == '_' {
|
||||
return true
|
||||
}
|
||||
|
||||
if b < '0' || b > 'z' {
|
||||
return false
|
||||
}
|
||||
|
||||
if b <= '9' {
|
||||
return true
|
||||
}
|
||||
|
||||
if b < 'A' {
|
||||
return false
|
||||
}
|
||||
|
||||
if b <= 'Z' {
|
||||
return true
|
||||
}
|
||||
|
||||
if b < 'a' {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// DecodeJWS decode an object encoded with the JWS serialized fromat
|
||||
// as specified by RFX 7515. to avoid an attack where the unprotected
|
||||
// JOSE header would contain a tempered signing algorithm, the Signer
|
||||
// should also be specified.
|
||||
func DecodeJWS(data []byte, v interface{}, s Signer) error {
|
||||
var headerLength, payloadLength int
|
||||
for i, c := range data {
|
||||
if isBase64URLEncoding(c) == true {
|
||||
continue
|
||||
}
|
||||
if c != '.' {
|
||||
return fmt.Errorf("jws: invalid serialization character %v in '%s'", c, data)
|
||||
}
|
||||
|
||||
if headerLength == 0 {
|
||||
if i == 0 {
|
||||
return fmt.Errorf("jws: invalid emtpy JOSE header in %s", data)
|
||||
}
|
||||
headerLength = i
|
||||
continue
|
||||
}
|
||||
|
||||
if payloadLength == 0 {
|
||||
if i-headerLength == 1 {
|
||||
return fmt.Errorf("jws: invalid emtpy payload in %s", data)
|
||||
}
|
||||
payloadLength = i - headerLength - 1
|
||||
continue
|
||||
}
|
||||
|
||||
return fmt.Errorf("jws: invalid third '.' in %s", data)
|
||||
}
|
||||
signatureLength := len(data) - 2 - headerLength - payloadLength
|
||||
signature := make([]byte, Base64DecodedLenFromStripped(signatureLength))
|
||||
signedLength := headerLength + payloadLength + 1
|
||||
Base64Decode(signature, data[signedLength+1:])
|
||||
|
||||
if err := s.Verify(data[:signedLength], signature); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//decode jose
|
||||
jose, err := DecodeJOSE(data[:headerLength])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if jose.Algorithm != s.Algorithm() {
|
||||
return fmt.Errorf("jws: Mismatched signing algorithm got %s, expected %s", jose.Algorithm, s.Algorithm())
|
||||
}
|
||||
|
||||
payload := make([]byte, Base64DecodedLenFromStripped(payloadLength))
|
||||
Base64Decode(payload, data[headerLength+1:signatureLength])
|
||||
//data is safe, just need to decode it.
|
||||
return json.Unmarshal(payload, v)
|
||||
}
|
||||
Reference in New Issue
Block a user