1package transit 2 3import ( 4 "context" 5 "crypto/ecdsa" 6 "crypto/elliptic" 7 "crypto/rsa" 8 "crypto/x509" 9 "encoding/base64" 10 "encoding/pem" 11 "errors" 12 "fmt" 13 "strconv" 14 "strings" 15 16 "github.com/hashicorp/vault/sdk/framework" 17 "github.com/hashicorp/vault/sdk/helper/keysutil" 18 "github.com/hashicorp/vault/sdk/logical" 19) 20 21const ( 22 exportTypeEncryptionKey = "encryption-key" 23 exportTypeSigningKey = "signing-key" 24 exportTypeHMACKey = "hmac-key" 25) 26 27func (b *backend) pathExportKeys() *framework.Path { 28 return &framework.Path{ 29 Pattern: "export/" + framework.GenericNameRegex("type") + "/" + framework.GenericNameRegex("name") + framework.OptionalParamRegex("version"), 30 Fields: map[string]*framework.FieldSchema{ 31 "type": &framework.FieldSchema{ 32 Type: framework.TypeString, 33 Description: "Type of key to export (encryption-key, signing-key, hmac-key)", 34 }, 35 "name": &framework.FieldSchema{ 36 Type: framework.TypeString, 37 Description: "Name of the key", 38 }, 39 "version": &framework.FieldSchema{ 40 Type: framework.TypeString, 41 Description: "Version of the key", 42 }, 43 }, 44 45 Callbacks: map[logical.Operation]framework.OperationFunc{ 46 logical.ReadOperation: b.pathPolicyExportRead, 47 }, 48 49 HelpSynopsis: pathExportHelpSyn, 50 HelpDescription: pathExportHelpDesc, 51 } 52} 53 54func (b *backend) pathPolicyExportRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 55 exportType := d.Get("type").(string) 56 name := d.Get("name").(string) 57 version := d.Get("version").(string) 58 59 switch exportType { 60 case exportTypeEncryptionKey: 61 case exportTypeSigningKey: 62 case exportTypeHMACKey: 63 default: 64 return logical.ErrorResponse(fmt.Sprintf("invalid export type: %s", exportType)), logical.ErrInvalidRequest 65 } 66 67 p, _, err := b.lm.GetPolicy(ctx, keysutil.PolicyRequest{ 68 Storage: req.Storage, 69 Name: name, 70 }) 71 if err != nil { 72 return nil, err 73 } 74 if p == nil { 75 return nil, nil 76 } 77 if !b.System().CachingDisabled() { 78 p.Lock(false) 79 } 80 defer p.Unlock() 81 82 if !p.Exportable { 83 return logical.ErrorResponse("key is not exportable"), nil 84 } 85 86 switch exportType { 87 case exportTypeEncryptionKey: 88 if !p.Type.EncryptionSupported() { 89 return logical.ErrorResponse("encryption not supported for the key"), logical.ErrInvalidRequest 90 } 91 case exportTypeSigningKey: 92 if !p.Type.SigningSupported() { 93 return logical.ErrorResponse("signing not supported for the key"), logical.ErrInvalidRequest 94 } 95 } 96 97 retKeys := map[string]string{} 98 switch version { 99 case "": 100 for k, v := range p.Keys { 101 exportKey, err := getExportKey(p, &v, exportType) 102 if err != nil { 103 return nil, err 104 } 105 retKeys[k] = exportKey 106 } 107 108 default: 109 var versionValue int 110 if version == "latest" { 111 versionValue = p.LatestVersion 112 } else { 113 version = strings.TrimPrefix(version, "v") 114 versionValue, err = strconv.Atoi(version) 115 if err != nil { 116 return logical.ErrorResponse("invalid key version"), logical.ErrInvalidRequest 117 } 118 } 119 120 if versionValue < p.MinDecryptionVersion { 121 return logical.ErrorResponse("version for export is below minimum decryption version"), logical.ErrInvalidRequest 122 } 123 key, ok := p.Keys[strconv.Itoa(versionValue)] 124 if !ok { 125 return logical.ErrorResponse("version does not exist or cannot be found"), logical.ErrInvalidRequest 126 } 127 128 exportKey, err := getExportKey(p, &key, exportType) 129 if err != nil { 130 return nil, err 131 } 132 133 retKeys[strconv.Itoa(versionValue)] = exportKey 134 } 135 136 resp := &logical.Response{ 137 Data: map[string]interface{}{ 138 "name": p.Name, 139 "type": p.Type.String(), 140 "keys": retKeys, 141 }, 142 } 143 144 return resp, nil 145} 146 147func getExportKey(policy *keysutil.Policy, key *keysutil.KeyEntry, exportType string) (string, error) { 148 if policy == nil { 149 return "", errors.New("nil policy provided") 150 } 151 152 switch exportType { 153 case exportTypeHMACKey: 154 return strings.TrimSpace(base64.StdEncoding.EncodeToString(key.HMACKey)), nil 155 156 case exportTypeEncryptionKey: 157 switch policy.Type { 158 case keysutil.KeyType_AES256_GCM96, keysutil.KeyType_ChaCha20_Poly1305: 159 return strings.TrimSpace(base64.StdEncoding.EncodeToString(key.Key)), nil 160 161 case keysutil.KeyType_RSA2048, keysutil.KeyType_RSA4096: 162 return encodeRSAPrivateKey(key.RSAKey), nil 163 } 164 165 case exportTypeSigningKey: 166 switch policy.Type { 167 case keysutil.KeyType_ECDSA_P256: 168 ecKey, err := keyEntryToECPrivateKey(key, elliptic.P256()) 169 if err != nil { 170 return "", err 171 } 172 return ecKey, nil 173 174 case keysutil.KeyType_ED25519: 175 return strings.TrimSpace(base64.StdEncoding.EncodeToString(key.Key)), nil 176 177 case keysutil.KeyType_RSA2048, keysutil.KeyType_RSA4096: 178 return encodeRSAPrivateKey(key.RSAKey), nil 179 } 180 } 181 182 return "", fmt.Errorf("unknown key type %v", policy.Type) 183} 184 185func encodeRSAPrivateKey(key *rsa.PrivateKey) string { 186 // When encoding PKCS1, the PEM header should be `RSA PRIVATE KEY`. When Go 187 // has PKCS8 encoding support, we may want to change this. 188 derBytes := x509.MarshalPKCS1PrivateKey(key) 189 pemBlock := &pem.Block{ 190 Type: "RSA PRIVATE KEY", 191 Bytes: derBytes, 192 } 193 pemBytes := pem.EncodeToMemory(pemBlock) 194 return string(pemBytes) 195} 196 197func keyEntryToECPrivateKey(k *keysutil.KeyEntry, curve elliptic.Curve) (string, error) { 198 if k == nil { 199 return "", errors.New("nil KeyEntry provided") 200 } 201 202 privKey := &ecdsa.PrivateKey{ 203 PublicKey: ecdsa.PublicKey{ 204 Curve: curve, 205 X: k.EC_X, 206 Y: k.EC_Y, 207 }, 208 D: k.EC_D, 209 } 210 ecder, err := x509.MarshalECPrivateKey(privKey) 211 if err != nil { 212 return "", err 213 } 214 if ecder == nil { 215 return "", errors.New("no data returned when marshalling to private key") 216 } 217 218 block := pem.Block{ 219 Type: "EC PRIVATE KEY", 220 Bytes: ecder, 221 } 222 return strings.TrimSpace(string(pem.EncodeToMemory(&block))), nil 223} 224 225const pathExportHelpSyn = `Export named encryption or signing key` 226 227const pathExportHelpDesc = ` 228This path is used to export the named keys that are configured as 229exportable. 230` 231