podman
363 строки · 10.2 Кб
1package libtrust2
3import (4"bytes"5"crypto"6"crypto/elliptic"7"crypto/tls"8"crypto/x509"9"encoding/base32"10"encoding/base64"11"encoding/binary"12"encoding/pem"13"errors"14"fmt"15"math/big"16"net/url"17"os"18"path/filepath"19"strings"20"time"21)
22
23// LoadOrCreateTrustKey will load a PrivateKey from the specified path
24func LoadOrCreateTrustKey(trustKeyPath string) (PrivateKey, error) {25if err := os.MkdirAll(filepath.Dir(trustKeyPath), 0700); err != nil {26return nil, err27}28
29trustKey, err := LoadKeyFile(trustKeyPath)30if err == ErrKeyFileDoesNotExist {31trustKey, err = GenerateECP256PrivateKey()32if err != nil {33return nil, fmt.Errorf("error generating key: %s", err)34}35
36if err := SaveKey(trustKeyPath, trustKey); err != nil {37return nil, fmt.Errorf("error saving key file: %s", err)38}39
40dir, file := filepath.Split(trustKeyPath)41if err := SavePublicKey(filepath.Join(dir, "public-"+file), trustKey.PublicKey()); err != nil {42return nil, fmt.Errorf("error saving public key file: %s", err)43}44} else if err != nil {45return nil, fmt.Errorf("error loading key file: %s", err)46}47return trustKey, nil48}
49
50// NewIdentityAuthTLSClientConfig returns a tls.Config configured to use identity
51// based authentication from the specified dockerUrl, the rootConfigPath and
52// the server name to which it is connecting.
53// If trustUnknownHosts is true it will automatically add the host to the
54// known-hosts.json in rootConfigPath.
55func NewIdentityAuthTLSClientConfig(dockerUrl string, trustUnknownHosts bool, rootConfigPath string, serverName string) (*tls.Config, error) {56tlsConfig := newTLSConfig()57
58trustKeyPath := filepath.Join(rootConfigPath, "key.json")59knownHostsPath := filepath.Join(rootConfigPath, "known-hosts.json")60
61u, err := url.Parse(dockerUrl)62if err != nil {63return nil, fmt.Errorf("unable to parse machine url")64}65
66if u.Scheme == "unix" {67return nil, nil68}69
70addr := u.Host71proto := "tcp"72
73trustKey, err := LoadOrCreateTrustKey(trustKeyPath)74if err != nil {75return nil, fmt.Errorf("unable to load trust key: %s", err)76}77
78knownHosts, err := LoadKeySetFile(knownHostsPath)79if err != nil {80return nil, fmt.Errorf("could not load trusted hosts file: %s", err)81}82
83allowedHosts, err := FilterByHosts(knownHosts, addr, false)84if err != nil {85return nil, fmt.Errorf("error filtering hosts: %s", err)86}87
88certPool, err := GenerateCACertPool(trustKey, allowedHosts)89if err != nil {90return nil, fmt.Errorf("Could not create CA pool: %s", err)91}92
93tlsConfig.ServerName = serverName94tlsConfig.RootCAs = certPool95
96x509Cert, err := GenerateSelfSignedClientCert(trustKey)97if err != nil {98return nil, fmt.Errorf("certificate generation error: %s", err)99}100
101tlsConfig.Certificates = []tls.Certificate{{102Certificate: [][]byte{x509Cert.Raw},103PrivateKey: trustKey.CryptoPrivateKey(),104Leaf: x509Cert,105}}106
107tlsConfig.InsecureSkipVerify = true108
109testConn, err := tls.Dial(proto, addr, tlsConfig)110if err != nil {111return nil, fmt.Errorf("tls Handshake error: %s", err)112}113
114opts := x509.VerifyOptions{115Roots: tlsConfig.RootCAs,116CurrentTime: time.Now(),117DNSName: tlsConfig.ServerName,118Intermediates: x509.NewCertPool(),119}120
121certs := testConn.ConnectionState().PeerCertificates122for i, cert := range certs {123if i == 0 {124continue125}126opts.Intermediates.AddCert(cert)127}128
129if _, err := certs[0].Verify(opts); err != nil {130if _, ok := err.(x509.UnknownAuthorityError); ok {131if trustUnknownHosts {132pubKey, err := FromCryptoPublicKey(certs[0].PublicKey)133if err != nil {134return nil, fmt.Errorf("error extracting public key from cert: %s", err)135}136
137pubKey.AddExtendedField("hosts", []string{addr})138
139if err := AddKeySetFile(knownHostsPath, pubKey); err != nil {140return nil, fmt.Errorf("error adding machine to known hosts: %s", err)141}142} else {143return nil, fmt.Errorf("unable to connect. unknown host: %s", addr)144}145}146}147
148testConn.Close()149tlsConfig.InsecureSkipVerify = false150
151return tlsConfig, nil152}
153
154// joseBase64UrlEncode encodes the given data using the standard base64 url
155// encoding format but with all trailing '=' characters omitted in accordance
156// with the jose specification.
157// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-2
158func joseBase64UrlEncode(b []byte) string {159return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")160}
161
162// joseBase64UrlDecode decodes the given string using the standard base64 url
163// decoder but first adds the appropriate number of trailing '=' characters in
164// accordance with the jose specification.
165// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-2
166func joseBase64UrlDecode(s string) ([]byte, error) {167s = strings.Replace(s, "\n", "", -1)168s = strings.Replace(s, " ", "", -1)169switch len(s) % 4 {170case 0:171case 2:172s += "=="173case 3:174s += "="175default:176return nil, errors.New("illegal base64url string")177}178return base64.URLEncoding.DecodeString(s)179}
180
181func keyIDEncode(b []byte) string {182s := strings.TrimRight(base32.StdEncoding.EncodeToString(b), "=")183var buf bytes.Buffer184var i int185for i = 0; i < len(s)/4-1; i++ {186start := i * 4187end := start + 4188buf.WriteString(s[start:end] + ":")189}190buf.WriteString(s[i*4:])191return buf.String()192}
193
194func keyIDFromCryptoKey(pubKey PublicKey) string {195// Generate and return a 'libtrust' fingerprint of the public key.196// For an RSA key this should be:197// SHA256(DER encoded ASN1)198// Then truncated to 240 bits and encoded into 12 base32 groups like so:199// ABCD:EFGH:IJKL:MNOP:QRST:UVWX:YZ23:4567:ABCD:EFGH:IJKL:MNOP200derBytes, err := x509.MarshalPKIXPublicKey(pubKey.CryptoPublicKey())201if err != nil {202return ""203}204hasher := crypto.SHA256.New()205hasher.Write(derBytes)206return keyIDEncode(hasher.Sum(nil)[:30])207}
208
209func stringFromMap(m map[string]interface{}, key string) (string, error) {210val, ok := m[key]211if !ok {212return "", fmt.Errorf("%q value not specified", key)213}214
215str, ok := val.(string)216if !ok {217return "", fmt.Errorf("%q value must be a string", key)218}219delete(m, key)220
221return str, nil222}
223
224func parseECCoordinate(cB64Url string, curve elliptic.Curve) (*big.Int, error) {225curveByteLen := (curve.Params().BitSize + 7) >> 3226
227cBytes, err := joseBase64UrlDecode(cB64Url)228if err != nil {229return nil, fmt.Errorf("invalid base64 URL encoding: %s", err)230}231cByteLength := len(cBytes)232if cByteLength != curveByteLen {233return nil, fmt.Errorf("invalid number of octets: got %d, should be %d", cByteLength, curveByteLen)234}235return new(big.Int).SetBytes(cBytes), nil236}
237
238func parseECPrivateParam(dB64Url string, curve elliptic.Curve) (*big.Int, error) {239dBytes, err := joseBase64UrlDecode(dB64Url)240if err != nil {241return nil, fmt.Errorf("invalid base64 URL encoding: %s", err)242}243
244// The length of this octet string MUST be ceiling(log-base-2(n)/8)245// octets (where n is the order of the curve). This is because the private246// key d must be in the interval [1, n-1] so the bitlength of d should be247// no larger than the bitlength of n-1. The easiest way to find the octet248// length is to take bitlength(n-1), add 7 to force a carry, and shift this249// bit sequence right by 3, which is essentially dividing by 8 and adding250// 1 if there is any remainder. Thus, the private key value d should be251// output to (bitlength(n-1)+7)>>3 octets.252n := curve.Params().N253octetLength := (new(big.Int).Sub(n, big.NewInt(1)).BitLen() + 7) >> 3254dByteLength := len(dBytes)255
256if dByteLength != octetLength {257return nil, fmt.Errorf("invalid number of octets: got %d, should be %d", dByteLength, octetLength)258}259
260return new(big.Int).SetBytes(dBytes), nil261}
262
263func parseRSAModulusParam(nB64Url string) (*big.Int, error) {264nBytes, err := joseBase64UrlDecode(nB64Url)265if err != nil {266return nil, fmt.Errorf("invalid base64 URL encoding: %s", err)267}268
269return new(big.Int).SetBytes(nBytes), nil270}
271
272func serializeRSAPublicExponentParam(e int) []byte {273// We MUST use the minimum number of octets to represent E.274// E is supposed to be 65537 for performance and security reasons275// and is what golang's rsa package generates, but it might be276// different if imported from some other generator.277buf := make([]byte, 4)278binary.BigEndian.PutUint32(buf, uint32(e))279var i int280for i = 0; i < 8; i++ {281if buf[i] != 0 {282break283}284}285return buf[i:]286}
287
288func parseRSAPublicExponentParam(eB64Url string) (int, error) {289eBytes, err := joseBase64UrlDecode(eB64Url)290if err != nil {291return 0, fmt.Errorf("invalid base64 URL encoding: %s", err)292}293// Only the minimum number of bytes were used to represent E, but294// binary.BigEndian.Uint32 expects at least 4 bytes, so we need295// to add zero padding if necassary.296byteLen := len(eBytes)297buf := make([]byte, 4-byteLen, 4)298eBytes = append(buf, eBytes...)299
300return int(binary.BigEndian.Uint32(eBytes)), nil301}
302
303func parseRSAPrivateKeyParamFromMap(m map[string]interface{}, key string) (*big.Int, error) {304b64Url, err := stringFromMap(m, key)305if err != nil {306return nil, err307}308
309paramBytes, err := joseBase64UrlDecode(b64Url)310if err != nil {311return nil, fmt.Errorf("invaled base64 URL encoding: %s", err)312}313
314return new(big.Int).SetBytes(paramBytes), nil315}
316
317func createPemBlock(name string, derBytes []byte, headers map[string]interface{}) (*pem.Block, error) {318pemBlock := &pem.Block{Type: name, Bytes: derBytes, Headers: map[string]string{}}319for k, v := range headers {320switch val := v.(type) {321case string:322pemBlock.Headers[k] = val323case []string:324if k == "hosts" {325pemBlock.Headers[k] = strings.Join(val, ",")326} else {327// Return error, non-encodable type328}329default:330// Return error, non-encodable type331}332}333
334return pemBlock, nil335}
336
337func pubKeyFromPEMBlock(pemBlock *pem.Block) (PublicKey, error) {338cryptoPublicKey, err := x509.ParsePKIXPublicKey(pemBlock.Bytes)339if err != nil {340return nil, fmt.Errorf("unable to decode Public Key PEM data: %s", err)341}342
343pubKey, err := FromCryptoPublicKey(cryptoPublicKey)344if err != nil {345return nil, err346}347
348addPEMHeadersToKey(pemBlock, pubKey)349
350return pubKey, nil351}
352
353func addPEMHeadersToKey(pemBlock *pem.Block, pubKey PublicKey) {354for key, value := range pemBlock.Headers {355var safeVal interface{}356if key == "hosts" {357safeVal = strings.Split(value, ",")358} else {359safeVal = value360}361pubKey.AddExtendedField(key, safeVal)362}363}
364