github.com/zntrio/harp/v2@v2.0.9/pkg/container/identity/key/json.go (about) 1 // Licensed to Elasticsearch B.V. under one or more contributor 2 // license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright 4 // ownership. Elasticsearch B.V. licenses this file to you under 5 // the Apache License, Version 2.0 (the "License"); you may 6 // not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, 12 // software distributed under the License is distributed on an 13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 // KIND, either express or implied. See the License for the 15 // specific language governing permissions and limitations 16 // under the License. 17 18 package key 19 20 import ( 21 "crypto/ecdsa" 22 "crypto/ed25519" 23 "crypto/elliptic" 24 "crypto/rand" 25 "crypto/sha512" 26 "encoding/base64" 27 "errors" 28 "fmt" 29 "math/big" 30 31 "github.com/zntrio/harp/v2/pkg/sdk/security/crypto/extra25519" 32 ) 33 34 // JSONWebKey holds internal container key attributes. 35 type JSONWebKey struct { 36 Kty string `json:"kty"` 37 Crv string `json:"crv"` 38 X string `json:"x,omitempty"` 39 Y string `json:"y,omitempty"` 40 D string `json:"d,omitempty"` 41 } 42 43 func (k *JSONWebKey) Sign(message []byte) (string, error) { 44 var sig []byte 45 46 // Decode private key 47 d, err := base64.RawURLEncoding.DecodeString(k.D) 48 if err != nil { 49 return "", fmt.Errorf("unable to decode private key: %w", err) 50 } 51 52 switch k.Crv { 53 case "Ed25519": 54 if len(d) != ed25519.PrivateKeySize { 55 return "", errors.New("invalid private key size") 56 } 57 58 // Sign the message 59 sig = ed25519.Sign(ed25519.PrivateKey(d), message) 60 case "P-384": 61 if len(d) != 48 { 62 return "", errors.New("invalid private key size") 63 } 64 65 // Rebuild the private key 66 var sk ecdsa.PrivateKey 67 sk.Curve = elliptic.P384() 68 sk.D = new(big.Int).SetBytes(d) 69 70 digest := sha512.Sum384(message) 71 r, s, err := ecdsa.Sign(rand.Reader, &sk, digest[:]) 72 if err != nil { 73 return "", fmt.Errorf("unable to sign the identity: %w", err) 74 } 75 76 // Assemble the signature 77 sig = append(r.Bytes(), s.Bytes()...) 78 } 79 80 // Encode the signature 81 return base64.RawURLEncoding.EncodeToString(sig), nil 82 } 83 84 // RecoveryKey returns the private encryption key from the private identity key. 85 func (k *JSONWebKey) RecoveryKey() (string, error) { 86 // Decode private key 87 privKeyRaw, err := base64.RawURLEncoding.DecodeString(k.D) 88 if err != nil { 89 return "", errors.New("invalid identity, private key is invalid") 90 } 91 92 switch k.Crv { 93 case "X25519": // Legacy keys 94 return base64.RawURLEncoding.EncodeToString(privKeyRaw), nil 95 case "Ed25519": 96 // Convert Ed25519 private key to x25519 key. 97 var sk [32]byte 98 extra25519.PrivateKeyToCurve25519(&sk, privKeyRaw) 99 return fmt.Sprintf("v1.ck.%s", base64.RawURLEncoding.EncodeToString(sk[:])), nil 100 case "P-384": 101 // FIPS compliant sealing process use ECDSA P-384 key. 102 return fmt.Sprintf("v2.ck.%s", base64.RawURLEncoding.EncodeToString(privKeyRaw)), nil 103 default: 104 } 105 106 // Unhandled key 107 return "", fmt.Errorf("unhandled private key format %q", k.Crv) 108 }