github.com/nats-io/jwt/v2@v2.5.6/v1compat/creds_utils.go (about)

     1  /*
     2   * Copyright 2019-2020 The NATS Authors
     3   * Licensed under the Apache License, Version 2.0 (the "License");
     4   * you may not use this file except in compliance with the License.
     5   * You may obtain a copy of the License at
     6   *
     7   * http://www.apache.org/licenses/LICENSE-2.0
     8   *
     9   * Unless required by applicable law or agreed to in writing, software
    10   * distributed under the License is distributed on an "AS IS" BASIS,
    11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12   * See the License for the specific language governing permissions and
    13   * limitations under the License.
    14   */
    15  
    16  package jwt
    17  
    18  import (
    19  	"bytes"
    20  	"errors"
    21  	"fmt"
    22  	"regexp"
    23  	"strings"
    24  
    25  	"github.com/nats-io/nkeys"
    26  )
    27  
    28  // DecorateJWT returns a decorated JWT that describes the kind of JWT
    29  func DecorateJWT(jwtString string) ([]byte, error) {
    30  	gc, err := DecodeGeneric(jwtString)
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  	return formatJwt(string(gc.Type), jwtString)
    35  }
    36  
    37  func formatJwt(kind string, jwtString string) ([]byte, error) {
    38  	templ := `-----BEGIN NATS %s JWT-----
    39  %s
    40  ------END NATS %s JWT------
    41  
    42  `
    43  	w := bytes.NewBuffer(nil)
    44  	kind = strings.ToUpper(kind)
    45  	_, err := fmt.Fprintf(w, templ, kind, jwtString, kind)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  	return w.Bytes(), nil
    50  }
    51  
    52  // DecorateSeed takes a seed and returns a string that wraps
    53  // the seed in the form:
    54  //
    55  //	************************* IMPORTANT *************************
    56  //	NKEY Seed printed below can be used sign and prove identity.
    57  //	NKEYs are sensitive and should be treated as secrets.
    58  //
    59  //	-----BEGIN USER NKEY SEED-----
    60  //	SUAIO3FHUX5PNV2LQIIP7TZ3N4L7TX3W53MQGEIVYFIGA635OZCKEYHFLM
    61  //	------END USER NKEY SEED------
    62  func DecorateSeed(seed []byte) ([]byte, error) {
    63  	w := bytes.NewBuffer(nil)
    64  	ts := bytes.TrimSpace(seed)
    65  	pre := string(ts[0:2])
    66  	kind := ""
    67  	switch pre {
    68  	case "SU":
    69  		kind = "USER"
    70  	case "SA":
    71  		kind = "ACCOUNT"
    72  	case "SO":
    73  		kind = "OPERATOR"
    74  	default:
    75  		return nil, errors.New("seed is not an operator, account or user seed")
    76  	}
    77  	header := `************************* IMPORTANT *************************
    78  NKEY Seed printed below can be used to sign and prove identity.
    79  NKEYs are sensitive and should be treated as secrets.
    80  
    81  -----BEGIN %s NKEY SEED-----
    82  `
    83  	_, err := fmt.Fprintf(w, header, kind)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	w.Write(ts)
    88  
    89  	footer := `
    90  ------END %s NKEY SEED------
    91  
    92  *************************************************************
    93  `
    94  	_, err = fmt.Fprintf(w, footer, kind)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  	return w.Bytes(), nil
    99  }
   100  
   101  var userConfigRE = regexp.MustCompile(`\s*(?:(?:[-]{3,}.*[-]{3,}\r?\n)([\w\-.=]+)(?:\r?\n[-]{3,}.*[-]{3,}(\r?\n|\z)))`)
   102  
   103  // An user config file looks like this:
   104  //  -----BEGIN NATS USER JWT-----
   105  //  eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5...
   106  //  ------END NATS USER JWT------
   107  //
   108  //  ************************* IMPORTANT *************************
   109  //  NKEY Seed printed below can be used sign and prove identity.
   110  //  NKEYs are sensitive and should be treated as secrets.
   111  //
   112  //  -----BEGIN USER NKEY SEED-----
   113  //  SUAIO3FHUX5PNV2LQIIP7TZ3N4L7TX3W53MQGEIVYFIGA635OZCKEYHFLM
   114  //  ------END USER NKEY SEED------
   115  
   116  // FormatUserConfig returns a decorated file with a decorated JWT and decorated seed
   117  func FormatUserConfig(jwtString string, seed []byte) ([]byte, error) {
   118  	gc, err := DecodeGeneric(jwtString)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	if gc.Type != UserClaim {
   123  		return nil, fmt.Errorf("%q cannot be serialized as a user config", string(gc.Type))
   124  	}
   125  
   126  	w := bytes.NewBuffer(nil)
   127  
   128  	jd, err := formatJwt(string(gc.Type), jwtString)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  	_, err = w.Write(jd)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  	if !bytes.HasPrefix(bytes.TrimSpace(seed), []byte("SU")) {
   137  		return nil, fmt.Errorf("nkey seed is not an user seed")
   138  	}
   139  
   140  	d, err := DecorateSeed(seed)
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  	_, err = w.Write(d)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	return w.Bytes(), nil
   150  }
   151  
   152  // ParseDecoratedJWT takes a creds file and returns the JWT portion.
   153  func ParseDecoratedJWT(contents []byte) (string, error) {
   154  	items := userConfigRE.FindAllSubmatch(contents, -1)
   155  	if len(items) == 0 {
   156  		return string(contents), nil
   157  	}
   158  	// First result should be the user JWT.
   159  	// We copy here so that if the file contained a seed file too we wipe appropriately.
   160  	raw := items[0][1]
   161  	tmp := make([]byte, len(raw))
   162  	copy(tmp, raw)
   163  	return string(tmp), nil
   164  }
   165  
   166  // ParseDecoratedNKey takes a creds file, finds the NKey portion and creates a
   167  // key pair from it.
   168  func ParseDecoratedNKey(contents []byte) (nkeys.KeyPair, error) {
   169  	var seed []byte
   170  
   171  	items := userConfigRE.FindAllSubmatch(contents, -1)
   172  	if len(items) > 1 {
   173  		seed = items[1][1]
   174  	} else {
   175  		lines := bytes.Split(contents, []byte("\n"))
   176  		for _, line := range lines {
   177  			if bytes.HasPrefix(bytes.TrimSpace(line), []byte("SO")) ||
   178  				bytes.HasPrefix(bytes.TrimSpace(line), []byte("SA")) ||
   179  				bytes.HasPrefix(bytes.TrimSpace(line), []byte("SU")) {
   180  				seed = line
   181  				break
   182  			}
   183  		}
   184  	}
   185  	if seed == nil {
   186  		return nil, errors.New("no nkey seed found")
   187  	}
   188  	if !bytes.HasPrefix(seed, []byte("SO")) &&
   189  		!bytes.HasPrefix(seed, []byte("SA")) &&
   190  		!bytes.HasPrefix(seed, []byte("SU")) {
   191  		return nil, errors.New("doesn't contain a seed nkey")
   192  	}
   193  	kp, err := nkeys.FromSeed(seed)
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  	return kp, nil
   198  }
   199  
   200  // ParseDecoratedUserNKey takes a creds file, finds the NKey portion and creates a
   201  // key pair from it. Similar to ParseDecoratedNKey but fails for non-user keys.
   202  func ParseDecoratedUserNKey(contents []byte) (nkeys.KeyPair, error) {
   203  	nk, err := ParseDecoratedNKey(contents)
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  	seed, err := nk.Seed()
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  	if !bytes.HasPrefix(seed, []byte("SU")) {
   212  		return nil, errors.New("doesn't contain an user seed nkey")
   213  	}
   214  	kp, err := nkeys.FromSeed(seed)
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  	return kp, nil
   219  }