github.com/trustbloc/kms-go@v1.1.2/kms/localkms/crypto_box.go (about) 1 /* 2 Copyright SecureKey Technologies Inc. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package localkms 8 9 import ( 10 "bytes" 11 "crypto/ed25519" 12 "errors" 13 "fmt" 14 "io" 15 16 "github.com/golang/protobuf/proto" 17 "github.com/google/tink/go/aead" 18 "github.com/google/tink/go/keyset" 19 ed25519pb "github.com/google/tink/go/proto/ed25519_go_proto" 20 tinkpb "github.com/google/tink/go/proto/tink_go_proto" 21 "golang.org/x/crypto/nacl/box" 22 23 "github.com/trustbloc/kms-go/doc/util/jwkkid" 24 "github.com/trustbloc/kms-go/kms/localkms/internal/keywrapper" 25 "github.com/trustbloc/kms-go/secretlock/noop" 26 "github.com/trustbloc/kms-go/util/cryptoutil" 27 28 "github.com/trustbloc/kms-go/spi/kms" 29 ) 30 31 // TODO: move CryptoBox out of the KMS package. 32 // this currently only sits inside LocalKMS so it can access private keys. See issue #511 33 // TODO delete this file and its corresponding test file when LegacyPacker is removed. 34 35 // CryptoBox provides an elliptic-curve-based authenticated encryption scheme 36 // 37 // Payloads are encrypted using symmetric encryption (XChacha20Poly1305) 38 // using a shared key derived from a shared secret created by 39 // 40 // Curve25519 Elliptic Curve Diffie-Hellman key exchange. 41 // 42 // CryptoBox is created by a KMS, and reads secret keys from the KMS 43 // 44 // for encryption/decryption, so clients do not need to see 45 // the secrets themselves. 46 type CryptoBox struct { 47 km *LocalKMS 48 } 49 50 // NewCryptoBox creates a CryptoBox which provides crypto box encryption using the given KMS's key. 51 func NewCryptoBox(w kms.KeyManager) (*CryptoBox, error) { 52 lkms, ok := w.(*LocalKMS) 53 if !ok { 54 return nil, fmt.Errorf("cannot use parameter argument as KMS") 55 } 56 57 return &CryptoBox{km: lkms}, nil 58 } 59 60 // Easy seals a message with a provided nonce 61 // theirPub is used as a public key, while myPub is used to identify the private key that should be used. 62 func (b *CryptoBox) Easy(payload, nonce, theirPub []byte, myKID string) ([]byte, error) { 63 var recPubBytes [cryptoutil.Curve25519KeySize]byte 64 65 copy(recPubBytes[:], theirPub) 66 67 senderPriv, err := b.km.exportEncPrivKeyBytes(myKID) 68 if err != nil { 69 return nil, fmt.Errorf("easy: failed to export sender key: %w, kid: %v", err, myKID) 70 } 71 72 var ( 73 priv [cryptoutil.Curve25519KeySize]byte 74 nonceBytes [cryptoutil.NonceSize]byte 75 ) 76 77 copy(priv[:], senderPriv) 78 copy(nonceBytes[:], nonce) 79 80 ret := box.Seal(nil, payload, &nonceBytes, &recPubBytes, &priv) 81 82 return ret, nil 83 } 84 85 // EasyOpen unseals a message sealed with Easy, where the nonce is provided 86 // theirPub is the public key used to decrypt directly, while myPub is used to identify the private key to be used. 87 func (b *CryptoBox) EasyOpen(cipherText, nonce, theirPub, myPub []byte) ([]byte, error) { 88 // myPub is used to get the recipient private key for decryption 89 var sendPubBytes [cryptoutil.Curve25519KeySize]byte 90 91 copy(sendPubBytes[:], theirPub) 92 93 kid, err := jwkkid.CreateKID(myPub, kms.ED25519Type) 94 if err != nil { 95 return nil, err 96 } 97 98 senderPriv, err := b.km.exportEncPrivKeyBytes(kid) 99 if err != nil { 100 return nil, err 101 } 102 103 var ( 104 priv [cryptoutil.Curve25519KeySize]byte 105 nonceBytes [cryptoutil.NonceSize]byte 106 ) 107 108 copy(priv[:], senderPriv) 109 copy(nonceBytes[:], nonce) 110 111 out, success := box.Open(nil, cipherText, &nonceBytes, &sendPubBytes, &priv) 112 if !success { 113 return nil, errors.New("failed to unpack") 114 } 115 116 return out, nil 117 } 118 119 // Seal seals a payload using the equivalent of libsodium box_seal 120 // 121 // Generates an ephemeral keypair to use for the sender, and includes 122 // the ephemeral sender public key in the message. 123 func (b *CryptoBox) Seal(payload, theirEncPub []byte, randSource io.Reader) ([]byte, error) { 124 // generate ephemeral curve25519 asymmetric keys 125 epk, esk, err := box.GenerateKey(randSource) 126 if err != nil { 127 return nil, err 128 } 129 130 var recPubBytes [cryptoutil.Curve25519KeySize]byte 131 132 copy(recPubBytes[:], theirEncPub) 133 134 nonce, err := cryptoutil.Nonce(epk[:], theirEncPub) 135 if err != nil { 136 return nil, err 137 } 138 139 // now seal the msg with the ephemeral key, nonce and recPub (which is recipient's publicKey) 140 ret := box.Seal(epk[:], payload, nonce, &recPubBytes, esk) 141 142 return ret, nil 143 } 144 145 // SealOpen decrypts a payload encrypted with Seal 146 // 147 // Reads the ephemeral sender public key, prepended to a properly-formatted message, 148 // and uses that along with the recipient private key corresponding to myPub to decrypt the message. 149 func (b *CryptoBox) SealOpen(cipherText, myPub []byte) ([]byte, error) { 150 if len(cipherText) < cryptoutil.Curve25519KeySize { 151 return nil, errors.New("message too short") 152 } 153 154 kid, err := jwkkid.CreateKID(myPub, kms.ED25519Type) 155 if err != nil { 156 return nil, fmt.Errorf("sealOpen: failed to compute ED25519 kid: %w", err) 157 } 158 159 recipientEncPriv, err := b.km.exportEncPrivKeyBytes(kid) 160 if err != nil { 161 return nil, fmt.Errorf("sealOpen: failed to exportPriveKeyBytes: %w", err) 162 } 163 164 var ( 165 epk [cryptoutil.Curve25519KeySize]byte 166 priv [cryptoutil.Curve25519KeySize]byte 167 ) 168 169 copy(epk[:], cipherText[:cryptoutil.Curve25519KeySize]) 170 copy(priv[:], recipientEncPriv) 171 172 recEncPub, err := cryptoutil.PublicEd25519toCurve25519(myPub) 173 if err != nil { 174 return nil, fmt.Errorf("sealOpen: failed to convert pub Ed25519 to X25519 key: %w", err) 175 } 176 177 nonce, err := cryptoutil.Nonce(epk[:], recEncPub) 178 if err != nil { 179 return nil, err 180 } 181 182 out, success := box.Open(nil, cipherText[cryptoutil.Curve25519KeySize:], nonce, &epk, &priv) 183 if !success { 184 return nil, errors.New("failed to unpack") 185 } 186 187 return out, nil 188 } 189 190 // exportEncPrivKeyBytes temporary support function for crypto_box to be used with legacyPacker only. 191 func (l *LocalKMS) exportEncPrivKeyBytes(id string) ([]byte, error) { 192 kh, err := l.getKeySet(id) 193 if err != nil { 194 return nil, err 195 } 196 197 buf := new(bytes.Buffer) 198 bWriter := keyset.NewBinaryWriter(buf) 199 200 kw, err := keywrapper.New(&noop.NoLock{}, "local-lock://tmp") 201 if err != nil { 202 return nil, err 203 } 204 205 primaryKeyEnvAEAD := aead.NewKMSEnvelopeAEAD2(aead.AES256GCMKeyTemplate(), kw) 206 207 err = kh.Write(bWriter, primaryKeyEnvAEAD) 208 if err != nil { 209 return nil, err 210 } 211 212 encryptedKS := &tinkpb.EncryptedKeyset{} 213 214 err = proto.Unmarshal(buf.Bytes(), encryptedKS) 215 if err != nil { 216 return nil, err 217 } 218 219 decryptedKS, err := primaryKeyEnvAEAD.Decrypt(encryptedKS.EncryptedKeyset, []byte{}) 220 if err != nil { 221 return nil, err 222 } 223 224 return extractPrivKey(decryptedKS) 225 } 226 227 func extractPrivKey(marshalledKeySet []byte) ([]byte, error) { 228 ks := &tinkpb.Keyset{} 229 230 err := proto.Unmarshal(marshalledKeySet, ks) 231 if err != nil { 232 return nil, err 233 } 234 235 for _, key := range ks.Key { 236 if key.KeyId != ks.PrimaryKeyId || key.Status != tinkpb.KeyStatusType_ENABLED { 237 continue 238 } 239 240 prvKey := &ed25519pb.Ed25519PrivateKey{} 241 242 err = proto.Unmarshal(key.KeyData.Value, prvKey) 243 if err != nil { 244 return nil, err 245 } 246 247 pkBytes := make([]byte, ed25519.PrivateKeySize) 248 copy(pkBytes[:ed25519.PublicKeySize], prvKey.KeyValue) 249 copy(pkBytes[ed25519.PublicKeySize:], prvKey.PublicKey.KeyValue) 250 251 return cryptoutil.SecretEd25519toCurve25519(pkBytes) 252 } 253 254 return nil, errors.New("private key not found") 255 }