591 lines
17 KiB
Go
591 lines
17 KiB
Go
// Package helpers implements utility functionality common to many
|
|
// CFSSL packages.
|
|
package helpers
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rsa"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"encoding/pem"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
|
|
"github.com/google/certificate-transparency-go"
|
|
cttls "github.com/google/certificate-transparency-go/tls"
|
|
ctx509 "github.com/google/certificate-transparency-go/x509"
|
|
"golang.org/x/crypto/ocsp"
|
|
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/cloudflare/cfssl/crypto/pkcs7"
|
|
cferr "github.com/cloudflare/cfssl/errors"
|
|
"github.com/cloudflare/cfssl/helpers/derhelpers"
|
|
"github.com/cloudflare/cfssl/log"
|
|
"golang.org/x/crypto/pkcs12"
|
|
)
|
|
|
|
// OneYear is a time.Duration representing a year's worth of seconds.
|
|
const OneYear = 8760 * time.Hour
|
|
|
|
// OneDay is a time.Duration representing a day's worth of seconds.
|
|
const OneDay = 24 * time.Hour
|
|
|
|
// InclusiveDate returns the time.Time representation of a date - 1
|
|
// nanosecond. This allows time.After to be used inclusively.
|
|
func InclusiveDate(year int, month time.Month, day int) time.Time {
|
|
return time.Date(year, month, day, 0, 0, 0, 0, time.UTC).Add(-1 * time.Nanosecond)
|
|
}
|
|
|
|
// Jul2012 is the July 2012 CAB Forum deadline for when CAs must stop
|
|
// issuing certificates valid for more than 5 years.
|
|
var Jul2012 = InclusiveDate(2012, time.July, 01)
|
|
|
|
// Apr2015 is the April 2015 CAB Forum deadline for when CAs must stop
|
|
// issuing certificates valid for more than 39 months.
|
|
var Apr2015 = InclusiveDate(2015, time.April, 01)
|
|
|
|
// KeyLength returns the bit size of ECDSA or RSA PublicKey
|
|
func KeyLength(key interface{}) int {
|
|
if key == nil {
|
|
return 0
|
|
}
|
|
if ecdsaKey, ok := key.(*ecdsa.PublicKey); ok {
|
|
return ecdsaKey.Curve.Params().BitSize
|
|
} else if rsaKey, ok := key.(*rsa.PublicKey); ok {
|
|
return rsaKey.N.BitLen()
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
// ExpiryTime returns the time when the certificate chain is expired.
|
|
func ExpiryTime(chain []*x509.Certificate) (notAfter time.Time) {
|
|
if len(chain) == 0 {
|
|
return
|
|
}
|
|
|
|
notAfter = chain[0].NotAfter
|
|
for _, cert := range chain {
|
|
if notAfter.After(cert.NotAfter) {
|
|
notAfter = cert.NotAfter
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// MonthsValid returns the number of months for which a certificate is valid.
|
|
func MonthsValid(c *x509.Certificate) int {
|
|
issued := c.NotBefore
|
|
expiry := c.NotAfter
|
|
years := (expiry.Year() - issued.Year())
|
|
months := years*12 + int(expiry.Month()) - int(issued.Month())
|
|
|
|
// Round up if valid for less than a full month
|
|
if expiry.Day() > issued.Day() {
|
|
months++
|
|
}
|
|
return months
|
|
}
|
|
|
|
// ValidExpiry determines if a certificate is valid for an acceptable
|
|
// length of time per the CA/Browser Forum baseline requirements.
|
|
// See https://cabforum.org/wp-content/uploads/CAB-Forum-BR-1.3.0.pdf
|
|
func ValidExpiry(c *x509.Certificate) bool {
|
|
issued := c.NotBefore
|
|
|
|
var maxMonths int
|
|
switch {
|
|
case issued.After(Apr2015):
|
|
maxMonths = 39
|
|
case issued.After(Jul2012):
|
|
maxMonths = 60
|
|
case issued.Before(Jul2012):
|
|
maxMonths = 120
|
|
}
|
|
|
|
if MonthsValid(c) > maxMonths {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// SignatureString returns the TLS signature string corresponding to
|
|
// an X509 signature algorithm.
|
|
func SignatureString(alg x509.SignatureAlgorithm) string {
|
|
switch alg {
|
|
case x509.MD2WithRSA:
|
|
return "MD2WithRSA"
|
|
case x509.MD5WithRSA:
|
|
return "MD5WithRSA"
|
|
case x509.SHA1WithRSA:
|
|
return "SHA1WithRSA"
|
|
case x509.SHA256WithRSA:
|
|
return "SHA256WithRSA"
|
|
case x509.SHA384WithRSA:
|
|
return "SHA384WithRSA"
|
|
case x509.SHA512WithRSA:
|
|
return "SHA512WithRSA"
|
|
case x509.DSAWithSHA1:
|
|
return "DSAWithSHA1"
|
|
case x509.DSAWithSHA256:
|
|
return "DSAWithSHA256"
|
|
case x509.ECDSAWithSHA1:
|
|
return "ECDSAWithSHA1"
|
|
case x509.ECDSAWithSHA256:
|
|
return "ECDSAWithSHA256"
|
|
case x509.ECDSAWithSHA384:
|
|
return "ECDSAWithSHA384"
|
|
case x509.ECDSAWithSHA512:
|
|
return "ECDSAWithSHA512"
|
|
default:
|
|
return "Unknown Signature"
|
|
}
|
|
}
|
|
|
|
// HashAlgoString returns the hash algorithm name contains in the signature
|
|
// method.
|
|
func HashAlgoString(alg x509.SignatureAlgorithm) string {
|
|
switch alg {
|
|
case x509.MD2WithRSA:
|
|
return "MD2"
|
|
case x509.MD5WithRSA:
|
|
return "MD5"
|
|
case x509.SHA1WithRSA:
|
|
return "SHA1"
|
|
case x509.SHA256WithRSA:
|
|
return "SHA256"
|
|
case x509.SHA384WithRSA:
|
|
return "SHA384"
|
|
case x509.SHA512WithRSA:
|
|
return "SHA512"
|
|
case x509.DSAWithSHA1:
|
|
return "SHA1"
|
|
case x509.DSAWithSHA256:
|
|
return "SHA256"
|
|
case x509.ECDSAWithSHA1:
|
|
return "SHA1"
|
|
case x509.ECDSAWithSHA256:
|
|
return "SHA256"
|
|
case x509.ECDSAWithSHA384:
|
|
return "SHA384"
|
|
case x509.ECDSAWithSHA512:
|
|
return "SHA512"
|
|
default:
|
|
return "Unknown Hash Algorithm"
|
|
}
|
|
}
|
|
|
|
// StringTLSVersion returns underlying enum values from human names for TLS
|
|
// versions, defaults to current golang default of TLS 1.0
|
|
func StringTLSVersion(version string) uint16 {
|
|
switch version {
|
|
case "1.2":
|
|
return tls.VersionTLS12
|
|
case "1.1":
|
|
return tls.VersionTLS11
|
|
default:
|
|
return tls.VersionTLS10
|
|
}
|
|
}
|
|
|
|
// EncodeCertificatesPEM encodes a number of x509 certificates to PEM
|
|
func EncodeCertificatesPEM(certs []*x509.Certificate) []byte {
|
|
var buffer bytes.Buffer
|
|
for _, cert := range certs {
|
|
pem.Encode(&buffer, &pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: cert.Raw,
|
|
})
|
|
}
|
|
|
|
return buffer.Bytes()
|
|
}
|
|
|
|
// EncodeCertificatePEM encodes a single x509 certificates to PEM
|
|
func EncodeCertificatePEM(cert *x509.Certificate) []byte {
|
|
return EncodeCertificatesPEM([]*x509.Certificate{cert})
|
|
}
|
|
|
|
// ParseCertificatesPEM parses a sequence of PEM-encoded certificate and returns them,
|
|
// can handle PEM encoded PKCS #7 structures.
|
|
func ParseCertificatesPEM(certsPEM []byte) ([]*x509.Certificate, error) {
|
|
var certs []*x509.Certificate
|
|
var err error
|
|
certsPEM = bytes.TrimSpace(certsPEM)
|
|
for len(certsPEM) > 0 {
|
|
var cert []*x509.Certificate
|
|
cert, certsPEM, err = ParseOneCertificateFromPEM(certsPEM)
|
|
if err != nil {
|
|
|
|
return nil, cferr.New(cferr.CertificateError, cferr.ParseFailed)
|
|
} else if cert == nil {
|
|
break
|
|
}
|
|
|
|
certs = append(certs, cert...)
|
|
}
|
|
if len(certsPEM) > 0 {
|
|
return nil, cferr.New(cferr.CertificateError, cferr.DecodeFailed)
|
|
}
|
|
return certs, nil
|
|
}
|
|
|
|
// ParseCertificatesDER parses a DER encoding of a certificate object and possibly private key,
|
|
// either PKCS #7, PKCS #12, or raw x509.
|
|
func ParseCertificatesDER(certsDER []byte, password string) (certs []*x509.Certificate, key crypto.Signer, err error) {
|
|
certsDER = bytes.TrimSpace(certsDER)
|
|
pkcs7data, err := pkcs7.ParsePKCS7(certsDER)
|
|
if err != nil {
|
|
var pkcs12data interface{}
|
|
certs = make([]*x509.Certificate, 1)
|
|
pkcs12data, certs[0], err = pkcs12.Decode(certsDER, password)
|
|
if err != nil {
|
|
certs, err = x509.ParseCertificates(certsDER)
|
|
if err != nil {
|
|
return nil, nil, cferr.New(cferr.CertificateError, cferr.DecodeFailed)
|
|
}
|
|
} else {
|
|
key = pkcs12data.(crypto.Signer)
|
|
}
|
|
} else {
|
|
if pkcs7data.ContentInfo != "SignedData" {
|
|
return nil, nil, cferr.Wrap(cferr.CertificateError, cferr.DecodeFailed, errors.New("can only extract certificates from signed data content info"))
|
|
}
|
|
certs = pkcs7data.Content.SignedData.Certificates
|
|
}
|
|
if certs == nil {
|
|
return nil, key, cferr.New(cferr.CertificateError, cferr.DecodeFailed)
|
|
}
|
|
return certs, key, nil
|
|
}
|
|
|
|
// ParseSelfSignedCertificatePEM parses a PEM-encoded certificate and check if it is self-signed.
|
|
func ParseSelfSignedCertificatePEM(certPEM []byte) (*x509.Certificate, error) {
|
|
cert, err := ParseCertificatePEM(certPEM)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := cert.CheckSignature(cert.SignatureAlgorithm, cert.RawTBSCertificate, cert.Signature); err != nil {
|
|
return nil, cferr.Wrap(cferr.CertificateError, cferr.VerifyFailed, err)
|
|
}
|
|
return cert, nil
|
|
}
|
|
|
|
// ParseCertificatePEM parses and returns a PEM-encoded certificate,
|
|
// can handle PEM encoded PKCS #7 structures.
|
|
func ParseCertificatePEM(certPEM []byte) (*x509.Certificate, error) {
|
|
certPEM = bytes.TrimSpace(certPEM)
|
|
cert, rest, err := ParseOneCertificateFromPEM(certPEM)
|
|
if err != nil {
|
|
// Log the actual parsing error but throw a default parse error message.
|
|
log.Debugf("Certificate parsing error: %v", err)
|
|
return nil, cferr.New(cferr.CertificateError, cferr.ParseFailed)
|
|
} else if cert == nil {
|
|
return nil, cferr.New(cferr.CertificateError, cferr.DecodeFailed)
|
|
} else if len(rest) > 0 {
|
|
return nil, cferr.Wrap(cferr.CertificateError, cferr.ParseFailed, errors.New("the PEM file should contain only one object"))
|
|
} else if len(cert) > 1 {
|
|
return nil, cferr.Wrap(cferr.CertificateError, cferr.ParseFailed, errors.New("the PKCS7 object in the PEM file should contain only one certificate"))
|
|
}
|
|
return cert[0], nil
|
|
}
|
|
|
|
// ParseOneCertificateFromPEM attempts to parse one PEM encoded certificate object,
|
|
// either a raw x509 certificate or a PKCS #7 structure possibly containing
|
|
// multiple certificates, from the top of certsPEM, which itself may
|
|
// contain multiple PEM encoded certificate objects.
|
|
func ParseOneCertificateFromPEM(certsPEM []byte) ([]*x509.Certificate, []byte, error) {
|
|
|
|
block, rest := pem.Decode(certsPEM)
|
|
if block == nil {
|
|
return nil, rest, nil
|
|
}
|
|
|
|
cert, err := x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
pkcs7data, err := pkcs7.ParsePKCS7(block.Bytes)
|
|
if err != nil {
|
|
return nil, rest, err
|
|
}
|
|
if pkcs7data.ContentInfo != "SignedData" {
|
|
return nil, rest, errors.New("only PKCS #7 Signed Data Content Info supported for certificate parsing")
|
|
}
|
|
certs := pkcs7data.Content.SignedData.Certificates
|
|
if certs == nil {
|
|
return nil, rest, errors.New("PKCS #7 structure contains no certificates")
|
|
}
|
|
return certs, rest, nil
|
|
}
|
|
var certs = []*x509.Certificate{cert}
|
|
return certs, rest, nil
|
|
}
|
|
|
|
// LoadPEMCertPool loads a pool of PEM certificates from file.
|
|
func LoadPEMCertPool(certsFile string) (*x509.CertPool, error) {
|
|
if certsFile == "" {
|
|
return nil, nil
|
|
}
|
|
pemCerts, err := ioutil.ReadFile(certsFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return PEMToCertPool(pemCerts)
|
|
}
|
|
|
|
// PEMToCertPool concerts PEM certificates to a CertPool.
|
|
func PEMToCertPool(pemCerts []byte) (*x509.CertPool, error) {
|
|
if len(pemCerts) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
certPool := x509.NewCertPool()
|
|
if !certPool.AppendCertsFromPEM(pemCerts) {
|
|
return nil, errors.New("failed to load cert pool")
|
|
}
|
|
|
|
return certPool, nil
|
|
}
|
|
|
|
// ParsePrivateKeyPEM parses and returns a PEM-encoded private
|
|
// key. The private key may be either an unencrypted PKCS#8, PKCS#1,
|
|
// or elliptic private key.
|
|
func ParsePrivateKeyPEM(keyPEM []byte) (key crypto.Signer, err error) {
|
|
return ParsePrivateKeyPEMWithPassword(keyPEM, nil)
|
|
}
|
|
|
|
// ParsePrivateKeyPEMWithPassword parses and returns a PEM-encoded private
|
|
// key. The private key may be a potentially encrypted PKCS#8, PKCS#1,
|
|
// or elliptic private key.
|
|
func ParsePrivateKeyPEMWithPassword(keyPEM []byte, password []byte) (key crypto.Signer, err error) {
|
|
keyDER, err := GetKeyDERFromPEM(keyPEM, password)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return derhelpers.ParsePrivateKeyDER(keyDER)
|
|
}
|
|
|
|
// GetKeyDERFromPEM parses a PEM-encoded private key and returns DER-format key bytes.
|
|
func GetKeyDERFromPEM(in []byte, password []byte) ([]byte, error) {
|
|
keyDER, _ := pem.Decode(in)
|
|
if keyDER != nil {
|
|
if procType, ok := keyDER.Headers["Proc-Type"]; ok {
|
|
if strings.Contains(procType, "ENCRYPTED") {
|
|
if password != nil {
|
|
return x509.DecryptPEMBlock(keyDER, password)
|
|
}
|
|
return nil, cferr.New(cferr.PrivateKeyError, cferr.Encrypted)
|
|
}
|
|
}
|
|
return keyDER.Bytes, nil
|
|
}
|
|
|
|
return nil, cferr.New(cferr.PrivateKeyError, cferr.DecodeFailed)
|
|
}
|
|
|
|
// ParseCSR parses a PEM- or DER-encoded PKCS #10 certificate signing request.
|
|
func ParseCSR(in []byte) (csr *x509.CertificateRequest, rest []byte, err error) {
|
|
in = bytes.TrimSpace(in)
|
|
p, rest := pem.Decode(in)
|
|
if p != nil {
|
|
if p.Type != "NEW CERTIFICATE REQUEST" && p.Type != "CERTIFICATE REQUEST" {
|
|
return nil, rest, cferr.New(cferr.CSRError, cferr.BadRequest)
|
|
}
|
|
|
|
csr, err = x509.ParseCertificateRequest(p.Bytes)
|
|
} else {
|
|
csr, err = x509.ParseCertificateRequest(in)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, rest, err
|
|
}
|
|
|
|
err = csr.CheckSignature()
|
|
if err != nil {
|
|
return nil, rest, err
|
|
}
|
|
|
|
return csr, rest, nil
|
|
}
|
|
|
|
// ParseCSRPEM parses a PEM-encoded certificate signing request.
|
|
// It does not check the signature. This is useful for dumping data from a CSR
|
|
// locally.
|
|
func ParseCSRPEM(csrPEM []byte) (*x509.CertificateRequest, error) {
|
|
block, _ := pem.Decode([]byte(csrPEM))
|
|
if block == nil {
|
|
return nil, cferr.New(cferr.CSRError, cferr.DecodeFailed)
|
|
}
|
|
csrObject, err := x509.ParseCertificateRequest(block.Bytes)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return csrObject, nil
|
|
}
|
|
|
|
// SignerAlgo returns an X.509 signature algorithm from a crypto.Signer.
|
|
func SignerAlgo(priv crypto.Signer) x509.SignatureAlgorithm {
|
|
switch pub := priv.Public().(type) {
|
|
case *rsa.PublicKey:
|
|
bitLength := pub.N.BitLen()
|
|
switch {
|
|
case bitLength >= 4096:
|
|
return x509.SHA512WithRSA
|
|
case bitLength >= 3072:
|
|
return x509.SHA384WithRSA
|
|
case bitLength >= 2048:
|
|
return x509.SHA256WithRSA
|
|
default:
|
|
return x509.SHA1WithRSA
|
|
}
|
|
case *ecdsa.PublicKey:
|
|
switch pub.Curve {
|
|
case elliptic.P521():
|
|
return x509.ECDSAWithSHA512
|
|
case elliptic.P384():
|
|
return x509.ECDSAWithSHA384
|
|
case elliptic.P256():
|
|
return x509.ECDSAWithSHA256
|
|
default:
|
|
return x509.ECDSAWithSHA1
|
|
}
|
|
default:
|
|
return x509.UnknownSignatureAlgorithm
|
|
}
|
|
}
|
|
|
|
// LoadClientCertificate load key/certificate from pem files
|
|
func LoadClientCertificate(certFile string, keyFile string) (*tls.Certificate, error) {
|
|
if certFile != "" && keyFile != "" {
|
|
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
|
if err != nil {
|
|
log.Critical("Unable to read client certificate from file: %s or key from file: %s", certFile, keyFile)
|
|
return nil, err
|
|
}
|
|
log.Debug("Client certificate loaded ")
|
|
return &cert, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// CreateTLSConfig creates a tls.Config object from certs and roots
|
|
func CreateTLSConfig(remoteCAs *x509.CertPool, cert *tls.Certificate) *tls.Config {
|
|
var certs []tls.Certificate
|
|
if cert != nil {
|
|
certs = []tls.Certificate{*cert}
|
|
}
|
|
return &tls.Config{
|
|
Certificates: certs,
|
|
RootCAs: remoteCAs,
|
|
}
|
|
}
|
|
|
|
// SerializeSCTList serializes a list of SCTs.
|
|
func SerializeSCTList(sctList []ct.SignedCertificateTimestamp) ([]byte, error) {
|
|
list := ctx509.SignedCertificateTimestampList{}
|
|
for _, sct := range sctList {
|
|
sctBytes, err := cttls.Marshal(sct)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
list.SCTList = append(list.SCTList, ctx509.SerializedSCT{Val: sctBytes})
|
|
}
|
|
return cttls.Marshal(list)
|
|
}
|
|
|
|
// DeserializeSCTList deserializes a list of SCTs.
|
|
func DeserializeSCTList(serializedSCTList []byte) ([]ct.SignedCertificateTimestamp, error) {
|
|
var sctList ctx509.SignedCertificateTimestampList
|
|
rest, err := cttls.Unmarshal(serializedSCTList, &sctList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(rest) != 0 {
|
|
return nil, cferr.Wrap(cferr.CTError, cferr.Unknown, errors.New("serialized SCT list contained trailing garbage"))
|
|
}
|
|
list := make([]ct.SignedCertificateTimestamp, len(sctList.SCTList))
|
|
for i, serializedSCT := range sctList.SCTList {
|
|
var sct ct.SignedCertificateTimestamp
|
|
rest, err := cttls.Unmarshal(serializedSCT.Val, &sct)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(rest) != 0 {
|
|
return nil, cferr.Wrap(cferr.CTError, cferr.Unknown, errors.New("serialized SCT contained trailing garbage"))
|
|
}
|
|
list[i] = sct
|
|
}
|
|
return list, nil
|
|
}
|
|
|
|
// SCTListFromOCSPResponse extracts the SCTList from an ocsp.Response,
|
|
// returning an empty list if the SCT extension was not found or could not be
|
|
// unmarshalled.
|
|
func SCTListFromOCSPResponse(response *ocsp.Response) ([]ct.SignedCertificateTimestamp, error) {
|
|
// This loop finds the SCTListExtension in the OCSP response.
|
|
var SCTListExtension, ext pkix.Extension
|
|
for _, ext = range response.Extensions {
|
|
// sctExtOid is the ObjectIdentifier of a Signed Certificate Timestamp.
|
|
sctExtOid := asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 5}
|
|
if ext.Id.Equal(sctExtOid) {
|
|
SCTListExtension = ext
|
|
break
|
|
}
|
|
}
|
|
|
|
// This code block extracts the sctList from the SCT extension.
|
|
var sctList []ct.SignedCertificateTimestamp
|
|
var err error
|
|
if numBytes := len(SCTListExtension.Value); numBytes != 0 {
|
|
var serializedSCTList []byte
|
|
rest := make([]byte, numBytes)
|
|
copy(rest, SCTListExtension.Value)
|
|
for len(rest) != 0 {
|
|
rest, err = asn1.Unmarshal(rest, &serializedSCTList)
|
|
if err != nil {
|
|
return nil, cferr.Wrap(cferr.CTError, cferr.Unknown, err)
|
|
}
|
|
}
|
|
sctList, err = DeserializeSCTList(serializedSCTList)
|
|
}
|
|
return sctList, err
|
|
}
|
|
|
|
// ReadBytes reads a []byte either from a file or an environment variable.
|
|
// If valFile has a prefix of 'env:', the []byte is read from the environment
|
|
// using the subsequent name. If the prefix is 'file:' the []byte is read from
|
|
// the subsequent file. If no prefix is provided, valFile is assumed to be a
|
|
// file path.
|
|
func ReadBytes(valFile string) ([]byte, error) {
|
|
switch splitVal := strings.SplitN(valFile, ":", 2); len(splitVal) {
|
|
case 1:
|
|
return ioutil.ReadFile(valFile)
|
|
case 2:
|
|
switch splitVal[0] {
|
|
case "env":
|
|
return []byte(os.Getenv(splitVal[1])), nil
|
|
case "file":
|
|
return ioutil.ReadFile(splitVal[1])
|
|
default:
|
|
return nil, fmt.Errorf("unknown prefix: %s", splitVal[0])
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("multiple prefixes: %s",
|
|
strings.Join(splitVal[:len(splitVal)-1], ", "))
|
|
}
|
|
}
|