-
Notifications
You must be signed in to change notification settings - Fork 500
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add Always Encrypted support #637
base: master
Are you sure you want to change the base?
Conversation
This commit adds partial support for the Microsoft SQL "Always Encrypted" feature (basically, E2E encryption). The current implementation is to be consider a __preview__ since it might not be perfectly implemented. Supported features: - PFX "keystore" - Seamless encryption Missing features: - Support for Private Keys that are not RSA - Encryption support (only Decryption is possible at the moment) The most probably needs to be improved a bit, but so far it's working for some of the use cases that I needed it for. Feel free to test the feature and open an issue if you find any problem: my goal is to have enough testers to spot eventual bugs. fix: lint issues
fcc2ec6
to
30c8baa
Compare
fix(alwaysEncrypted): fix NBCRow feat(go.mod): update mssql-always-encrypted to v0.1.3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is unlikely to go further unless you bring the encryption into this package. I would recommend opening a new PR for that if you are interested in that.
I would also want to start with encryption parameters in the Connector.
|
||
db, err := sql.Open("sqlserver", u.String()) | ||
if err != nil { | ||
logrus.Fatalf("unable to open db: %v", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this example, use return fmt.Errorf("unable to open db: %w", err)
throughout.
} | ||
rows, err := db.Query("SELECT id, ssn FROM [dbo].[cid]") | ||
if err != nil { | ||
logrus.Fatalf("unable to perform query: %v", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above.
if err != nil { | ||
logrus.Fatalf("unable to open db: %v", err) | ||
} | ||
rows, err := db.Query("SELECT id, ssn FROM [dbo].[cid]") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove [
and ]
.
logrus.Fatalf("unable to perform query: %v", err) | ||
} | ||
|
||
for ; rows.Next(); { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Go, there are three for loop variants: 0 argument (loop forever), 1 argument (test condition), and 3 argument.
You want the 1 argument version here: for rows.Next() {
} | ||
err = rows.Scan(&dest.Id, &dest.SSN) | ||
if err != nil { | ||
logrus.Fatalf("unable to scan into struct: %v", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above.
defer rows.Close() | ||
|
||
if err != nil { | ||
t.Fatalf("unable to query db: %s", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The verb should be %w, not %s.
import ( | ||
"fmt" | ||
"testing" | ||
"github.com/stretchr/testify/assert" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove. We don't need an assert package.
@@ -0,0 +1,29 @@ | |||
package mssql | |||
|
|||
type cekTable struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is cek mean? A comment or full name would be good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Column Encryption Key, this is used everywhere in Microsoft's documentation. Not sure if it's a good idea to make it longer here since cek
is mentioned everywhere in the codebase now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, then above type cekTable struct {
, put
// Column Encryption Key table that ...
|
||
type cekTableEntry struct { | ||
databaseID int | ||
keyId int |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
KeyID
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why should this be exported?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh nevermind, you were referring to the ID part 😅
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe | ||
github.com/stretchr/testify v1.7.0 | ||
github.com/swisscom/mssql-always-encrypted v0.1.3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a non-starter.
If you want to bring this into this repo for review, we can do that. But we won't be adding an external repo like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, but I wanted to keep the two things separated to not clog too much this repo, especially since one package is feature complete (encryption /+ decryption) but here we only support encryption for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would belong in an internal/ package, but not a different repo.
Thanks for the review! I'll try to find the time to improve the PR |
any idea how to Insert or update the encrypted data? |
@denysvitali are you interested in re-homing this PR in our fork? https://github.com/microsoft/go-mssqldb |
Sure! |
} | ||
|
||
// TODO: Support other private keys | ||
rootKey, err := cekv.Decrypt(s.alwaysEncryptedSettings.pKey.(*rsa.PrivateKey)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as we consider porting this over to the microsoft fork - how can we factor out the key providers into separate packages and make the core agnostic to which key provider is chosen?
IE, we want to have an app to be able to import whichever key provider package they want, and we would implement the AKV provider package and perhaps the local PFX file provider.
Then the connection string can specify the name of the key provider along with its authentication parameters. Each provider would be responsible for parsing its auth parameters from the connection string.
I've started a more extensive AE implementation in the Microsoft fork and welcome feedback. We're starting with decryption using local certs or Azure Key Vault then expanding to encryption. microsoft#116 |
panic(err) | ||
} | ||
|
||
pk, cert, err := pkcs12.Decode(pfxBytes, s.alwaysEncryptedSettings.ksSecret) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@denysvitali Hi Denys. I am experimenting with supporting the Windows certificate store to try to get certificate bytes and private key bytes to feed into your existing decryption code.
Unfortunately the encoded cert bytes referenced in a windows CERT_CONTEXT structure are not the same as the contents of the pfx file so pkcs12.Decode fails:
panic: pkcs12: error reading P12 data: asn1: structure error: tags don't match (2 vs {class:0 tag:16 length:559 isCompound:true}) {optional:false explicit:false application:false private:false defaultValue:<nil> tag:<nil> stringType:0 timeType:0 set:false omitEmpty:false} int @4 [recovered]
Do you happen to have some insight on the best way to get the raw bytes of the private key from the Windows cryptapi ?
I can get the x509Certificate thusly:
func certContextToX509(certHandle *windows.CertContext) (*x509.Certificate, error) {
var der []byte
slice := (*reflect.SliceHeader)(unsafe.Pointer(&der))
slice.Data = uintptr(unsafe.Pointer(certHandle.EncodedCert))
slice.Len = int(certHandle.Length)
slice.Cap = int(certHandle.Length)
return x509.ParseCertificate(der)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a Windows user, but I recall Microsoft actually preventing in some cases to export the private key (I think there is a flag somewhere).
Maybe this is the case for you (or you get an encrypted version of it) and thus you cannot parse it :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh ncryptexportkey looks promising...
@denysvitali @kardianos Does go-mssqldb have an equivalent function for converting the exec sp_describe_parameter_encryption N'DECLARE @c1 AS NCHAR (10) = @p3ee82b8b457c44498ce61053237ba60e;
DECLARE @c2 AS INT = @p643017f69684494b9143f6de7c9078b1;
INSERT INTO Table_1
VALUES (@c1, @c2);
',N'@p3ee82b8b457c44498ce61053237ba60e nchar(10),@p643017f69684494b9143f6de7c9078b1 int' |
@denysvitali I've got a PR for both encryption and decryption in the Microsoft fork at microsoft#116. |
This commit adds partial support for the Microsoft SQL "Always Encrypted" feature (basically, E2E encryption).
The current implementation is to be consider a preview since it might not be perfectly implemented.
Supported features:
Missing features:
The most probably needs to be improved a bit, but so far it's working for some of the use cases that I needed it for.
Feel free to test the feature and open an issue if you find any problem: my goal is to have enough testers to spot eventual bugs.
Fixes #265