Skip to content

Instantly share code, notes, and snippets.

@AdamMagaluk
Last active December 12, 2022 14:36
Show Gist options
  • Save AdamMagaluk/cac70132399fe53d52c7636bb08cb96d to your computer and use it in GitHub Desktop.
Save AdamMagaluk/cac70132399fe53d52c7636bb08cb96d to your computer and use it in GitHub Desktop.
{
"alg": "RS256",
"typ": "JWT",
"x5c": [
"MIICvzCCAaegAwIBAgICBnowDQYJKoZIhvcNAQELBQAwADAgFw0yMjEyMTIxNDMyNDRaGA8yMTIyMTIxMjE0MzI0NFowADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ9ZAcATK9bnV39w2Z4/COdXRqExVHLaPamfrVps6fKEEUK6HF5XbkrUt6Ifv0BNE31KBbsY5X2MZ08GUbYFiBjLu3wqZBV+pAHaWNTU7u98PMzsNyryJa6uAcDvLWpGVI2oJCbUNRJL8nX0Gfn3/PI9iXx+vUbfCcm01D7AdsWz+fcLxxna48p/wvMresxpXPBfOYJKh/h10FM+Sq/+5AGXtkZEwJ8J1/xlS1YrhqlJce3kA3u6lixjp+Lav44kwfPlDcZxLT+nXvPZxSbGZuCLlr8HO47+9lAEjybvApcSeav+YEFBonMSeBxdT+N4+cKQxb2WHaNCi3GOa5SXLfcCAwEAAaNBMD8wDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAOBgNVHQ4EBwQFAQIDBAYwDQYJKoZIhvcNAQELBQADggEBAHIc4VBoziyHZFAuIrxtVGYaSAw6XT85zbybDOIu4syfgbQoSJWdhreYEtBoe0n7wdGIQjydIy6fcaHlU4fWZnD2Vp3WP7biiVBDzAzjeXluy57lIZe0pRsjIAww4tO3Pesu+UIVoS5DNgoilKuKcbQMKtz891TXNqbOXtDSYF/oPgbCwoajUiwiInq5eRFV9MXqPFtD0VvNwzaTrcScZ0k8w1ZPVurYDE16jbjoV5yBHwevbpetPzsIiq9gzYnDAVVoJyWZNeiDpux/9sXzCUMXQojywoTOaNxlys2VMyoo0dMeLuyF4dwW8+ZYDO+x3iB17a0OozZPQBDY78KTR14"
],
"x5t": "giXGhVKE4nfI8fBCKuMGM35xmyE"
}
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsIng1YyI6WyJNSUlDdnpDQ0FhZWdBd0lCQWdJQ0Jub3dEUVlKS29aSWh2Y05BUUVMQlFBd0FEQWdGdzB5TWpFeU1USXhORE15TkRSYUdBOHlNVEl5TVRJeE1qRTBNekkwTkZvd0FEQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQUo5WkFjQVRLOWJuVjM5dzJaNC9DT2RYUnFFeFZITGFQYW1mclZwczZmS0VFVUs2SEY1WGJrclV0NklmdjBCTkUzMUtCYnNZNVgyTVowOEdVYllGaUJqTHUzd3FaQlYrcEFIYVdOVFU3dTk4UE16c055cnlKYTZ1QWNEdkxXcEdWSTJvSkNiVU5SSkw4blgwR2ZuMy9QSTlpWHgrdlViZkNjbTAxRDdBZHNXeitmY0x4eG5hNDhwL3d2TXJlc3hwWFBCZk9ZSktoL2gxMEZNK1NxLys1QUdYdGtaRXdKOEoxL3hsUzFZcmhxbEpjZTNrQTN1NmxpeGpwK0xhdjQ0a3dmUGxEY1p4TFQrblh2UFp4U2JHWnVDTGxyOEhPNDcrOWxBRWp5YnZBcGNTZWF2K1lFRkJvbk1TZUJ4ZFQrTjQrY0tReGIyV0hhTkNpM0dPYTVTWExmY0NBd0VBQWFOQk1EOHdEZ1lEVlIwUEFRSC9CQVFEQWdlQU1CMEdBMVVkSlFRV01CUUdDQ3NHQVFVRkJ3TUNCZ2dyQmdFRkJRY0RBVEFPQmdOVkhRNEVCd1FGQVFJREJBWXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBSEljNFZCb3ppeUhaRkF1SXJ4dFZHWWFTQXc2WFQ4NXpieWJET0l1NHN5ZmdiUW9TSldkaHJlWUV0Qm9lMG43d2RHSVFqeWRJeTZmY2FIbFU0ZldabkQyVnAzV1A3YmlpVkJEekF6amVYbHV5NTdsSVplMHBSc2pJQXd3NHRPM1Blc3UrVUlWb1M1RE5nb2lsS3VLY2JRTUt0ejg5MVRYTnFiT1h0RFNZRi9vUGdiQ3dvYWpVaXdpSW5xNWVSRlY5TVhxUEZ0RDBWdk53emFUcmNTY1owazh3MVpQVnVyWURFMTZqYmpvVjV5Qkh3ZXZicGV0UHpzSWlxOWd6WW5EQVZWb0p5V1pOZWlEcHV4LzlzWHpDVU1YUW9qeXdvVE9hTnhseXMyVk15b28wZE1lTHV5RjRkd1c4K1pZRE8reDNpQjE3YTBPb3paUFFCRFk3OEtUUjE0Il0sIng1dCI6ImdpWEdoVktFNG5mSThmQkNLdU1HTTM1eG15RSJ9.eyJzdWIiOiJTVUIiLCJhdWQiOlsiQVVEMSJdfQ.hAO18jv5YFCqnBJbjrN0hSV4Idkad1wajCpQuhjfoZmjCFahJlyNDcfmL4B0lRkcP2gd0yb2cqn9Y5ac1KzYeu02k3Vre3UQ3MGr-6Ly_aaUjS-Tia6RKnNgKZ11LD0aXMKxX_5c8cVTn1a8f-8D06WfCx5_jB2LU7sLQqh--qov9nOWD3_3Ky0OtSLmErZhGmvP6011XfxdVApAAtOq6jBPbw9U9j7i86eeZior1I8g-iCOjFk1x2FCwha5fjOZHqU0avSM6298IJ96itAPkMqVe9Sk8_yoTq0NxpYnZklY2ckht881BX3Ox3a_wwyuykdrA6GoCijF67mMKw198Q
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"crypto/x509/pkix"
"encoding/base64"
"encoding/binary"
"errors"
"fmt"
"log"
"math/big"
"time"
"github.com/golang-jwt/jwt/v4"
)
func main() {
log.Printf("Usage: verify_certificate SERVER_NAME CERT.pem CHAIN.pem")
caCert, caPrivKey, err := createCA()
if err != nil {
log.Fatal(err)
}
roots := x509.NewCertPool()
roots.AddCert(caCert)
opts := x509.VerifyOptions{
Roots: roots,
// DNSName: "robot.app.viam.dev",
Intermediates: x509.NewCertPool(),
}
robotCert, robotPrivKey, err := createSignedCert(caCert, caPrivKey)
if err != nil {
log.Fatal(err)
}
tokenString := createToken(robotPrivKey, robotCert)
log.Println(tokenString)
_, err = verifyToken(tokenString, opts)
if err != nil {
log.Fatal(err)
}
badRobotCert, badRobotPrivKey, err := createSelfSignedCert()
if err != nil {
log.Fatal(err)
}
tokenString = createToken(badRobotPrivKey, badRobotCert)
_, err = verifyToken(tokenString, opts)
if err == nil {
log.Fatal("Expected self-signed cert to fail")
}
}
func createCA() (*x509.Certificate, *rsa.PrivateKey, error) {
ca := &x509.Certificate{
SerialNumber: big.NewInt(2019),
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(100, 0, 0),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
caPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey)
if err != nil {
return nil, nil, err
}
caCert, err := x509.ParseCertificate(caBytes)
if err != nil {
return nil, nil, err
}
return caCert, caPrivKey, nil
}
func createSignedCert(ca *x509.Certificate, caPrivKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) {
certIn := &x509.Certificate{
SerialNumber: big.NewInt(1658),
Subject: pkix.Name{},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(100, 0, 0),
SubjectKeyId: []byte{1, 2, 3, 4, 6},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature,
}
certPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
certBytes, err := x509.CreateCertificate(rand.Reader, certIn, ca, &certPrivKey.PublicKey, caPrivKey)
if err != nil {
return nil, nil, err
}
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
return nil, nil, err
}
return cert, certPrivKey, nil
}
func createSelfSignedCert() (*x509.Certificate, *rsa.PrivateKey, error) {
certIn := &x509.Certificate{
SerialNumber: big.NewInt(1659),
Subject: pkix.Name{},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(100, 0, 0),
SubjectKeyId: []byte{1, 2, 3, 4, 6},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature,
}
certPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
certBytes, err := x509.CreateCertificate(rand.Reader, certIn, certIn, &certPrivKey.PublicKey, certPrivKey)
if err != nil {
return nil, nil, err
}
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
return nil, nil, err
}
return cert, certPrivKey, nil
}
func createToken(privKey *rsa.PrivateKey, cert *x509.Certificate) string {
// TODO(RSDK-890): use the correct subject, not the audience (entity)
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.RegisteredClaims{
Subject: "SUB",
Audience: jwt.ClaimStrings{"AUD1"},
})
token.Header["x5c"] = []string{
base64.RawStdEncoding.EncodeToString(cert.Raw),
}
thumbPrint := sha1.New()
thumbPrint.Write(cert.Raw)
token.Header["x5t"] = base64.RawURLEncoding.EncodeToString(thumbPrint.Sum(nil))
tokenString, err := token.SignedString(privKey)
if err != nil {
panic(err)
}
return tokenString
}
func verifyToken(tokenString string, verifyOpts x509.VerifyOptions) (*jwt.Token, error) {
jwtParser := jwt.NewParser(jwt.WithoutClaimsValidation())
outToken, err := jwtParser.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if token.Method.Alg() != "RS256" {
return nil, errors.New("only allowed RS256")
}
x5cRaw, ok := token.Header["x5c"]
if !ok {
return nil, errors.New("missing x5c in header1 ")
}
x5cArr, ok := x5cRaw.([]interface{})
if !ok || len(x5cArr) == 0 {
return nil, errors.New("invalid x5c in header 2")
}
certStr := x5cArr[0].(string)
certBytes, err := base64.RawStdEncoding.DecodeString(certStr)
if err != nil {
return nil, err
}
robotCert, err := x509.ParseCertificate(certBytes)
if err != nil {
return nil, err
}
robotPub, ok := robotCert.PublicKey.(*rsa.PublicKey)
if !ok {
return nil, errors.New("invalid public key")
}
if _, err := robotCert.Verify(verifyOpts); err != nil {
return nil, err
}
return robotPub, nil
})
if err != nil {
return nil, err
}
return outToken, nil
}
func EncodeUint64ToString(v uint64) string {
data := make([]byte, 8)
binary.BigEndian.PutUint64(data, v)
i := 0
for ; i < len(data); i++ {
if data[i] != 0x0 {
break
}
}
return base64.RawURLEncoding.EncodeToString(data[i:])
}
func DecodeUint64FromString(v string) (uint64, error) {
data, err := DecodeString(v)
if err != nil {
return 0, err
}
bigInt := big.NewInt(0).SetBytes(data)
return bigInt.Uint64(), nil
}
func Decode(src []byte) ([]byte, error) {
dst := make([]byte, base64.RawURLEncoding.DecodedLen(len(src)))
n, err := base64.RawURLEncoding.Decode(dst, src)
if err != nil {
return nil, fmt.Errorf(`failed to decode source: %w`, err)
}
return dst[:n], nil
}
func DecodeString(src string) ([]byte, error) {
return Decode([]byte(src))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment