@@ -113,8 +113,8 @@ func (c *Client) EnrollPFXV2(ea *EnrollPFXFctArgsV2) (*EnrollResponseV2, error)
113113 var missingFields []string
114114
115115 // TODO: Probably a better way to express these if blocks
116- if ea .Template == "" {
117- missingFields = append (missingFields , "Template" )
116+ if ea .Template == "" && ea . EnrollmentPatternId == 0 {
117+ missingFields = append (missingFields , "Template or EnrollmentPatternId " )
118118 }
119119 if ea .CertificateAuthority == "" {
120120 missingFields = append (missingFields , "CertificateAuthority" )
@@ -151,7 +151,11 @@ func (c *Client) EnrollPFXV2(ea *EnrollPFXFctArgsV2) (*EnrollResponseV2, error)
151151 }
152152 ea .SubjectString = subject
153153 } else {
154- return nil , fmt .Errorf ("subject is required to use enrollpfx(). Please configure either SubjectString or Subject" )
154+ log .Println ("[DEBUG] EnrollPFXV2: Subject is nil checks if there are SANs" )
155+ if ea .SANs == nil || (len (ea .SANs .DNS ) == 0 && len (ea .SANs .URI ) == 0 && len (ea .SANs .IP4 ) == 0 &&
156+ len (ea .SANs .IP6 ) == 0 ) {
157+ return nil , fmt .Errorf ("subject or subject alternative names are required to use enrollpfx(). Please configure either SubjectString or Subject or SANs" )
158+ }
155159 }
156160 }
157161
@@ -191,12 +195,16 @@ func (c *Client) EnrollPFXV2(ea *EnrollPFXFctArgsV2) (*EnrollResponseV2, error)
191195// Returns:
192196// - Leaf certificate
193197// - Certificate chain
198+ // - Raw certificate data (as base64 string, if applicable)
199+ // - Error
194200func (c * Client ) DownloadCertificate (
195201 certId int ,
196202 thumbprint string ,
197203 serialNumber string ,
198204 issuerDn string ,
199- ) (* x509.Certificate , []* x509.Certificate , error ) {
205+ collectionId int ,
206+ certificateFormat string ,
207+ ) (* x509.Certificate , []* x509.Certificate , * string , error ) {
200208 log .Println ("[INFO] Downloading certificate" )
201209
202210 /* The download certificate endpoint requires one of the following to retrieve a cert:
@@ -216,7 +224,7 @@ func (c *Client) DownloadCertificate(
216224 }
217225
218226 if ! validInput {
219- return nil , nil , fmt .Errorf ("certID, thumbprint, or serial number AND issuer DN required to dowload certificate" )
227+ return nil , nil , nil , fmt .Errorf ("certID, thumbprint, or serial number AND issuer DN required to dowload certificate" )
220228 }
221229
222230 payload := & downloadCertificateBody {
@@ -228,12 +236,33 @@ func (c *Client) DownloadCertificate(
228236 ChainOrder : "EndEntityFirst" ,
229237 }
230238
239+ query := apiQuery {
240+ Query : []StringTuple {},
241+ }
242+ if collectionId > 0 {
243+ log .Println ("[DEBUG] RecoverCertificate: Collection ID:" , collectionId )
244+ query .Query = append (
245+ query .Query , StringTuple {
246+ "collectionId" , fmt .Sprintf ("%d" , collectionId ),
247+ },
248+ )
249+ log .Println ("[DEBUG] RecoverCertificate: Query:" , query )
250+ }
251+
231252 // Set Keyfactor-specific headers
253+ switch certificateFormat {
254+ case "CER" , "CRT" , "DER" , "PEM" :
255+ // do nothing these are valid formats
256+ break
257+ default :
258+ // if not specified or invalid format then default to P7B
259+ certificateFormat = "P7B"
260+ }
232261 headers := & apiHeaders {
233262 Headers : []StringTuple {
234263 {"x-keyfactor-api-version" , "1" },
235264 {"x-keyfactor-requested-with" , "APIClient" },
236- {"x-certificateformat" , "P7B" },
265+ {"x-certificateformat" , certificateFormat },
237266 },
238267 }
239268
@@ -242,17 +271,18 @@ func (c *Client) DownloadCertificate(
242271 Endpoint : "Certificates/Download" ,
243272 Headers : headers ,
244273 Payload : payload ,
274+ Query : & query ,
245275 }
246276
247277 resp , err := c .sendRequest (keyfactorAPIStruct )
248278 if err != nil {
249- return nil , nil , err
279+ return nil , nil , nil , err
250280 }
251281
252282 jsonResp := & downloadCertificateResponse {}
253283 err = json .NewDecoder (resp .Body ).Decode (& jsonResp )
254284 if err != nil {
255- return nil , nil , err
285+ return nil , nil , nil , err
256286 }
257287 //buf, err := base64.StdEncoding.DecodeString(jsonResp.Content)
258288 //if err != nil {
@@ -266,17 +296,17 @@ func (c *Client) DownloadCertificate(
266296
267297 certs , p7bErr := ConvertBase64P7BtoCertificates (jsonResp .Content )
268298 if p7bErr != nil {
269- return nil , nil , p7bErr
299+ return nil , nil , & jsonResp . Content , p7bErr
270300 }
271301
272302 var leaf * x509.Certificate
273303 if len (certs ) > 1 {
274304 //leaf is last cert in chain
275305 leaf = certs [0 ] // First cert in chain is the leaf
276- return leaf , certs , nil
306+ return leaf , certs , & jsonResp . Content , nil
277307 }
278308
279- return certs [0 ], nil , nil
309+ return certs [0 ], nil , & jsonResp . Content , nil
280310}
281311
282312// EnrollCSR takes arguments for EnrollCSRFctArgs to enroll a passed Certificate Signing
@@ -644,7 +674,8 @@ func (c *Client) RecoverCertificate(
644674 issuerDn string ,
645675 password string ,
646676 collectionId int ,
647- ) (interface {}, * x509.Certificate , []* x509.Certificate , error ) {
677+ certificateFormat string ,
678+ ) (interface {}, * x509.Certificate , []* x509.Certificate , * string , error ) {
648679 log .Println ("[DEBUG] Enter RecoverCertificate" )
649680 log .Println ("[INFO] Recovering certificate ID:" , certId )
650681 /* The download certificate endpoint requires one of the following to retrieve a cert:
@@ -654,6 +685,9 @@ func (c *Client) RecoverCertificate(
654685
655686 Check for this input
656687 */
688+ if certificateFormat == "" {
689+ certificateFormat = "PFX"
690+ }
657691 validInput := false
658692 if certId != 0 {
659693 validInput = true
@@ -665,12 +699,12 @@ func (c *Client) RecoverCertificate(
665699
666700 if ! validInput {
667701 log .Println ("[ERROR] RecoverCertificate: certID, thumbprint, or serial number AND issuer DN required to download certificate" )
668- return nil , nil , nil , fmt .Errorf ("certID, thumbprint, or serial number AND issuer DN required to download certificate" )
702+ return nil , nil , nil , nil , fmt .Errorf ("certID, thumbprint, or serial number AND issuer DN required to download certificate" )
669703 }
670704 log .Println ("[DEBUG] RecoverCertificate: Valid input" )
671705
672706 if password == "" {
673- return nil , nil , nil , fmt .Errorf ("password required to recover private key with certificate" )
707+ return nil , nil , nil , nil , fmt .Errorf ("password required to recover private key with certificate" )
674708 }
675709
676710 rca := & recoverCertArgs {
@@ -688,7 +722,7 @@ func (c *Client) RecoverCertificate(
688722 Headers : []StringTuple {
689723 {"x-keyfactor-api-version" , "1" },
690724 {"x-keyfactor-requested-with" , "APIClient" },
691- {"x-certificateformat" , "PFX" },
725+ {"x-certificateformat" , certificateFormat },
692726 },
693727 }
694728
@@ -719,65 +753,192 @@ func (c *Client) RecoverCertificate(
719753 resp , err := c .sendRequest (keyfactorAPIStruct )
720754 if err != nil {
721755 log .Println ("[ERROR] RecoverCertificate: Error recovering certificate from Keyfactor Command" , err .Error ())
722- return nil , nil , nil , err
756+ return nil , nil , nil , nil , err
723757 }
724758
725759 jsonResp := & recoverCertResponse {}
726760 log .Println ("[DEBUG] RecoverCertificate: Decoding response" )
727761 err = json .NewDecoder (resp .Body ).Decode (& jsonResp )
728762 if err != nil {
729763 log .Println ("[ERROR] RecoverCertificate: Error decoding response from Keyfactor Command" , err .Error ())
730- return nil , nil , nil , err
764+ return nil , nil , nil , nil , err
731765 }
732766
733- log .Println ("[DEBUG] RecoverCertificate: Decoding PFX" )
734- pfxDer , err := base64 .StdEncoding .DecodeString (jsonResp .PFX )
735- if err != nil {
736- log .Println ("[ERROR] RecoverCertificate: Error decoding PFX" , err .Error ())
737- return nil , nil , nil , err
767+ switch certificateFormat {
768+ case "PFX" , "pfx" , "pkcs12" , "p12" , "jks" , "JKS" :
769+ log .Println ("[DEBUG] RecoverCertificate: decoding `PFX` response field" )
770+ pfxDer := jsonResp .PFX
771+ if pfxDer == "" {
772+ log .Println ("[ERROR] RecoverCertificate: Error decoding PFX" , err .Error ())
773+ return nil , nil , nil , & pfxDer , fmt .Errorf ("pfx field in response is empty" )
774+ }
775+ log .Println ("[INFO] Recovered certificate successfully" )
776+ log .Println ("[DEBUG] RecoverCertificate returning in PFX format" )
777+ return nil , nil , nil , & pfxDer , nil
778+ case "PEM" , "pem" :
779+ log .Println ("[DEBUG] RecoverCertificate: Decoding PFX" )
780+ pfxDer , dErr := base64 .StdEncoding .DecodeString (jsonResp .PFX )
781+ if dErr != nil {
782+ log .Println ("[ERROR] RecoverCertificate: Error decoding PFX" , dErr .Error ())
783+ return nil , nil , nil , & jsonResp .PFX , dErr
784+ }
785+
786+ log .Println ("[DEBUG] RecoverCertificate: Decoding PFX chain" )
787+ priv , leaf , chain , pErr := pkcs12 .DecodeChain (pfxDer , rca .Password )
788+ if pErr != nil {
789+ log .Println ("[ERROR] RecoverCertificate: Error decoding PFX chain" , pErr .Error ())
790+ return nil , nil , nil , & jsonResp .PFX , pErr
791+ }
792+
793+ log .Println ("[INFO] Recovered certificate successfully" )
794+ log .Println ("[DEBUG] RecoverCertificate: " , leaf , chain )
795+ return priv , leaf , chain , & jsonResp .PFX , nil
796+ default :
797+ log .Println ("[DEBUG] RecoverCertificate: Decoding PFX" )
798+ pfxDer , dErr := base64 .StdEncoding .DecodeString (jsonResp .PFX )
799+ if dErr != nil {
800+ log .Println ("[ERROR] RecoverCertificate: Error decoding PFX" , dErr .Error ())
801+ return nil , nil , nil , & jsonResp .PFX , dErr
802+ }
803+
804+ log .Println ("[DEBUG] RecoverCertificate: Decoding PFX chain" )
805+ priv , leaf , chain , pErr := pkcs12 .DecodeChain (pfxDer , rca .Password )
806+ if pErr != nil {
807+ log .Println ("[ERROR] RecoverCertificate: Error decoding PFX chain" , pErr .Error ())
808+ return nil , nil , nil , & jsonResp .PFX , pErr
809+ }
810+
811+ log .Println ("[INFO] Recovered certificate successfully" )
812+ log .Println ("[DEBUG] RecoverCertificate returning in PEM format" )
813+
814+ var pemCerts []string
815+
816+ // Encode leaf certificate to PEM
817+ pemLeaf := pem .EncodeToMemory (
818+ & pem.Block {
819+ Type : "CERTIFICATE" ,
820+ Bytes : leaf .Raw ,
821+ },
822+ )
823+ pemCerts = append (pemCerts , string (pemLeaf ))
824+
825+ // Encode chain certificates to PEM
826+ for _ , cert := range chain {
827+ pemCert := pem .EncodeToMemory (
828+ & pem.Block {
829+ Type : "CERTIFICATE" ,
830+ Bytes : cert .Raw ,
831+ },
832+ )
833+ pemCerts = append (pemCerts , string (pemCert ))
834+ }
835+
836+ pemData := strings .Join (pemCerts , "\n " )
837+ return priv , leaf , chain , & pemData , nil
838+ }
839+
840+ }
841+
842+ // ChangeCertificateOwnerRole changes the certificate's owner. Users must be in the current owner's role and the new owner's role.
843+ // If removing the owner, leave both NewRoleId and NewRoleName empty in the request.
844+ // Calls PUT /Certificates/{id}/Owner endpoint.
845+ func (c * Client ) ChangeCertificateOwnerRole (
846+ certificateId int ,
847+ req * OwnerRequest ,
848+ params ... * CertificateOwnerChangeParams ,
849+ ) error {
850+ log .Printf ("[INFO] Changing owner of certificate with ID %d in Keyfactor" , certificateId )
851+
852+ // Validate certificate ID
853+ if certificateId <= 0 {
854+ return errors .New ("certificate ID must be a positive integer" )
738855 }
739856
740- log .Println ("[DEBUG] RecoverCertificate: Decoding PFX chain" )
741- priv , leaf , chain , err := pkcs12 .DecodeChain (pfxDer , rca .Password )
857+ // Set Keyfactor-specific headers
858+ headers := & apiHeaders {
859+ Headers : []StringTuple {
860+ {"x-keyfactor-api-version" , "1" },
861+ {"x-keyfactor-requested-with" , "APIClient" },
862+ {"Content-Type" , "application/json" },
863+ },
864+ }
865+
866+ // Build URL with query parameters
867+ endpoint := fmt .Sprintf ("Certificates/%d/Owner" , certificateId )
868+ var queryParams []string
869+
870+ if len (params ) > 0 && params [0 ] != nil {
871+ param := params [0 ]
872+ if param .CollectionId != nil {
873+ queryParams = append (queryParams , fmt .Sprintf ("collectionId=%d" , * param .CollectionId ))
874+ }
875+ if param .ContainerId != nil {
876+ queryParams = append (queryParams , fmt .Sprintf ("containerId=%d" , * param .ContainerId ))
877+ }
878+ }
879+
880+ if len (queryParams ) > 0 {
881+ endpoint += "?" + strings .Join (queryParams , "&" )
882+ }
883+
884+ keyfactorAPIStruct := & request {
885+ Method : "PUT" ,
886+ Endpoint : endpoint ,
887+ Headers : headers ,
888+ Payload : req ,
889+ }
890+
891+ resp , err := c .sendRequest (keyfactorAPIStruct )
742892 if err != nil {
743- log .Println ("[ERROR] RecoverCertificate: Error decoding PFX chain" , err .Error ())
744- return nil , nil , nil , err
893+ return err
894+ }
895+
896+ // Check if the response indicates success (204 No Content expected)
897+ if resp .StatusCode != http .StatusNoContent {
898+ return fmt .Errorf ("failed to change certificate owner: HTTP %d" , resp .StatusCode )
745899 }
746900
747- log .Println ("[INFO] Recovered certificate successfully" )
748- log .Println ("[DEBUG] RecoverCertificate: " , leaf , chain )
749- return priv , leaf , chain , nil
901+ return nil
750902}
751903
752904// createSubject builds the certificate subject string from a passed CertificateSubject argument.
753905func createSubject (cs CertificateSubject ) (string , error ) {
754906 var subject string
755907
756908 if cs .SubjectCommonName != "" && cs .SubjectCommonName != "<null>" {
757- subject = "CN=" + cs .SubjectCommonName + ","
909+ subject = "CN=" + escapeDNValue ( cs .SubjectCommonName ) + ","
758910 } else {
759911 return "" , errors .New ("build subject: common name required" ) // Common name is required!
760912 }
761913 if cs .SubjectOrganizationalUnit != "" && cs .SubjectOrganizationalUnit != "<null>" {
762- subject += "OU=" + cs .SubjectOrganizationalUnit + ","
914+ subject += "OU=" + escapeDNValue ( cs .SubjectOrganizationalUnit ) + ","
763915 }
764916 if cs .SubjectOrganization != "" && cs .SubjectOrganization != "<null>" {
765- subject += "O=" + cs .SubjectOrganization + ","
917+ subject += "O=" + escapeDNValue ( cs .SubjectOrganization ) + ","
766918 }
767919 if cs .SubjectLocality != "" && cs .SubjectLocality != "<null>" {
768- subject += "L=" + cs .SubjectLocality + ","
920+ subject += "L=" + escapeDNValue ( cs .SubjectLocality ) + ","
769921 }
770922 if cs .SubjectState != "" && cs .SubjectState != "<null>" {
771- subject += "ST=" + cs .SubjectState + ","
923+ subject += "ST=" + escapeDNValue ( cs .SubjectState ) + ","
772924 }
773925 if cs .SubjectCountry != "" && cs .SubjectCountry != "<null>" {
774- subject += "C=" + cs .SubjectCountry + ","
926+ subject += "C=" + escapeDNValue ( cs .SubjectCountry ) + ","
775927 }
776928 subject = strings .TrimRight (subject , "," ) // remove trailing comma
777929 log .Printf ("[DEBUG] createSubject(): Certificate subject created: %s\n " , subject )
778930 return subject , nil
779931}
780932
933+ // escapeDNValue ensures that a value in a DN is properly escaped if it contains special characters.
934+ func escapeDNValue (value string ) string {
935+ // If the value contains a comma, quote it
936+ if strings .Contains (value , "," ) {
937+ return `"` + value + `"`
938+ }
939+ return value
940+ }
941+
781942// validateDeployPFXArgs validates the arguments required to deploy a PFX certificate.
782943func validateDeployPFXArgs (dpfxa * DeployPFXArgs ) error {
783944 if dpfxa .StoreIds == nil {
0 commit comments