You only specify the password to decrypt the private key file with. You need to add a password to store the certificate with:
CREATE CERTIFICATE ERecruitStatsGatheringCert
ENCRYPTION BY PASSWORD = 'S3creT!
FROM FILE = 'd:\DavesCert.cer'
WITH PRIVATE KEY (
FILE = 'd:\DavesCert.pvk',
DECRYPTION BY PASSWORD = 'S3creT!');
But your Background information makes the whole exercise futile. You're doing it wrong. The correct sequence of actions is:
- Create a cert in the user DB
- Add the signature to the store procedure(s)
- Drop the private key of the certificate
- Backup/restore that cert to
master
(public key only!)
- Create login in
master
, assigned that permission, etc.
Notice that not only the private key never leaves the database, is in fact explicitly dropped right after signing the procedure. This is required in order to prevent further use of this certificate to sign other procedures and abuse the login permissions created at step 5. You repeat these steps on each database and use a different certificate on each database. You repeat these steps each time you modify any of the signed procedure and generate a new certificate each time.
As a general rule, any signature/encryption public/private key (RSA) scheme that requires a copy of the private key is broken. This is why is called private and this is what gives value to the signature: the knowledge that there exists only one instance of this private key in the world, therefore anything signed by it is proof that it came from the one and only owner/holder of the unique private key.
I know there are some who shun my recommendation of dropping the private key right after is used to sign the procedure. I stand by my recommendation, but the important thing for your problem is that it is not required to copy the private key into [master]
in order to leverage the the code signing permission you desire. You can (and should) create the login derived from the certificate using just the public key.
Regarding automation: As I see it, basically it is a tool (script, app) that takes two inputs: a securable (procedure, function, module) and a permission (VIEW SERVER STATE
). Everything else is an automated process (create a one-time-use throw-away cert, sign, export cert, import cert in [master]
, create login, grant permission). The names implied (cert name, login name) can all be generated.
Proliferation of names is a valid concern. An alternative would be to use only one certificate and associated private key, and the signature tool could add the private key when needed (when signing the procedure), then remove it after the signing. Is really a matter of how you run your shop and how important is the asset you're protecting. But the important thing is that you do not need to import the private key in [master]
.
Haven't tested this on other versions of Visual Studio, but with version 12.0.3 (Update4) there is an option to override the ANSI Nulls config at the database-level specification for a single procedure in an SSDT project.
Just right-click the procedure in solution explorer and choose properties...
Best Answer
Before getting into any specifics about Certificate creation and module signing, we can at least verify whether or not the two instances have the same certificate. Run the following on both instances:
If the Certificates are the same, then all of those fields will have identical values across both instances (or both databases on the same instance). But you mainly just need to check the
thumbprint
field since it is an SHA-1 hash of the certificate. If that value is different between the two locations, then you aren't dealing with the same certificate.You say that
but do not mention exactly how the Certificates are being created. They can be generated in SQL Server (this is known as "self-signed") by using the following syntax:
This method will generate a new private key (each time it is run) and encrypt that with the password specified in the
ENCRYPTION BY PASSWORD
clause else encrypted by the database master key (which is why it is better to specify the password).Or, you can create them by providing an existing Assembly, File, or Binary literal:
The
FILE
andBINARY
methods allow for optionally specifying a private key file or binary literal. So if using one of those methods along with specifying the private key, or if using the signedASSEMBLY
, then creating a certificate this way should provide you with the same private key each time.So how do you get the
FILE
orBINARY
values?FILE:
Once you have created the initial Certificate, export it (including the private key!) using the following:
And in the new place:
BINARY: (only available in SQL Server 2012 and newer)
Once you have created the initial Certificate, get the certificate and private key values using the following:
And in the new place:
If you are using SQL Server 2012 or newer, then the
BINARY
method would be the easiest since it is completely self-contained (i.e. doesn't rely upon any external files).PLEASE NOTE:
WITH SIGNATURE
clause ofADD SIGNATURE
since it is only used when the private key was removed or not used in the creation of the Certificate.In this case (i.e. all of those fields in
sys.certificates
matching and using the private key in theCREATE CERTIFICATE
), signing modules in two different locations that have the exact same definition (as in: byte-by-byte the same), will produce the same signatureWITH SIGNATURE
clause and pass in the binary literal of the signature that was generated by the same private key and same module definition from a location that at least at one point did have the private key. This is a more secure set up because it only allows for signing modules that you provide the signatures for. If a Certificate does not have a private key, it can still be used to check the validity of signed modules, but it cannot be used to sign new or changed modules (which is kinda cool).BUT, if:
ADD SIGNATURE
is being used in the destination database using theWITH SIGNATURE
clause,the binary literal being used is the same as found in the source database via:
then you need to check the
OBJECT_DEFINITION
for that module in both locations. They need to be identical, including casing, etc. You can do aSELECT CONVERT(VARBINARY(MAX), OBJECT_DEFINITION(OBJECT_ID(N'{module_name}')))
just to be sure. It is possible that the schema comparison is ignoring differences in comments and/or the casing of theCREATE
keyword at the beginning of the definition and/or some white-space, etc.Please see the following MSDN pages for details: