Skip to content

Commit 8019d9d

Browse files
authored
Merge pull request #49 from Keyfactor/v25_enrollment_updates
V25 enrollment updates
2 parents 1bc7e47 + 6868250 commit 8019d9d

File tree

5 files changed

+726
-60
lines changed

5 files changed

+726
-60
lines changed

v3/api/certificate.go

Lines changed: 166 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -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,13 +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
194200
func (c *Client) DownloadCertificate(
195201
certId int,
196202
thumbprint string,
197203
serialNumber string,
198204
issuerDn string,
199205
collectionId int,
200-
) (*x509.Certificate, []*x509.Certificate, error) {
206+
certificateFormat string,
207+
) (*x509.Certificate, []*x509.Certificate, *string, error) {
201208
log.Println("[INFO] Downloading certificate")
202209

203210
/* The download certificate endpoint requires one of the following to retrieve a cert:
@@ -217,7 +224,7 @@ func (c *Client) DownloadCertificate(
217224
}
218225

219226
if !validInput {
220-
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")
221228
}
222229

223230
payload := &downloadCertificateBody{
@@ -243,11 +250,19 @@ func (c *Client) DownloadCertificate(
243250
}
244251

245252
// 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+
}
246261
headers := &apiHeaders{
247262
Headers: []StringTuple{
248263
{"x-keyfactor-api-version", "1"},
249264
{"x-keyfactor-requested-with", "APIClient"},
250-
{"x-certificateformat", "P7B"},
265+
{"x-certificateformat", certificateFormat},
251266
},
252267
}
253268

@@ -261,13 +276,13 @@ func (c *Client) DownloadCertificate(
261276

262277
resp, err := c.sendRequest(keyfactorAPIStruct)
263278
if err != nil {
264-
return nil, nil, err
279+
return nil, nil, nil, err
265280
}
266281

267282
jsonResp := &downloadCertificateResponse{}
268283
err = json.NewDecoder(resp.Body).Decode(&jsonResp)
269284
if err != nil {
270-
return nil, nil, err
285+
return nil, nil, nil, err
271286
}
272287
//buf, err := base64.StdEncoding.DecodeString(jsonResp.Content)
273288
//if err != nil {
@@ -281,17 +296,17 @@ func (c *Client) DownloadCertificate(
281296

282297
certs, p7bErr := ConvertBase64P7BtoCertificates(jsonResp.Content)
283298
if p7bErr != nil {
284-
return nil, nil, p7bErr
299+
return nil, nil, &jsonResp.Content, p7bErr
285300
}
286301

287302
var leaf *x509.Certificate
288303
if len(certs) > 1 {
289304
//leaf is last cert in chain
290305
leaf = certs[0] // First cert in chain is the leaf
291-
return leaf, certs, nil
306+
return leaf, certs, &jsonResp.Content, nil
292307
}
293308

294-
return certs[0], nil, nil
309+
return certs[0], nil, &jsonResp.Content, nil
295310
}
296311

297312
// EnrollCSR takes arguments for EnrollCSRFctArgs to enroll a passed Certificate Signing
@@ -659,7 +674,8 @@ func (c *Client) RecoverCertificate(
659674
issuerDn string,
660675
password string,
661676
collectionId int,
662-
) (interface{}, *x509.Certificate, []*x509.Certificate, error) {
677+
certificateFormat string,
678+
) (interface{}, *x509.Certificate, []*x509.Certificate, *string, error) {
663679
log.Println("[DEBUG] Enter RecoverCertificate")
664680
log.Println("[INFO] Recovering certificate ID:", certId)
665681
/* The download certificate endpoint requires one of the following to retrieve a cert:
@@ -669,6 +685,9 @@ func (c *Client) RecoverCertificate(
669685
670686
Check for this input
671687
*/
688+
if certificateFormat == "" {
689+
certificateFormat = "PFX"
690+
}
672691
validInput := false
673692
if certId != 0 {
674693
validInput = true
@@ -680,12 +699,12 @@ func (c *Client) RecoverCertificate(
680699

681700
if !validInput {
682701
log.Println("[ERROR] RecoverCertificate: certID, thumbprint, or serial number AND issuer DN required to download certificate")
683-
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")
684703
}
685704
log.Println("[DEBUG] RecoverCertificate: Valid input")
686705

687706
if password == "" {
688-
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")
689708
}
690709

691710
rca := &recoverCertArgs{
@@ -703,7 +722,7 @@ func (c *Client) RecoverCertificate(
703722
Headers: []StringTuple{
704723
{"x-keyfactor-api-version", "1"},
705724
{"x-keyfactor-requested-with", "APIClient"},
706-
{"x-certificateformat", "PFX"},
725+
{"x-certificateformat", certificateFormat},
707726
},
708727
}
709728

@@ -734,34 +753,152 @@ func (c *Client) RecoverCertificate(
734753
resp, err := c.sendRequest(keyfactorAPIStruct)
735754
if err != nil {
736755
log.Println("[ERROR] RecoverCertificate: Error recovering certificate from Keyfactor Command", err.Error())
737-
return nil, nil, nil, err
756+
return nil, nil, nil, nil, err
738757
}
739758

740759
jsonResp := &recoverCertResponse{}
741760
log.Println("[DEBUG] RecoverCertificate: Decoding response")
742761
err = json.NewDecoder(resp.Body).Decode(&jsonResp)
743762
if err != nil {
744763
log.Println("[ERROR] RecoverCertificate: Error decoding response from Keyfactor Command", err.Error())
745-
return nil, nil, nil, err
764+
return nil, nil, nil, nil, err
746765
}
747766

748-
log.Println("[DEBUG] RecoverCertificate: Decoding PFX")
749-
pfxDer, err := base64.StdEncoding.DecodeString(jsonResp.PFX)
750-
if err != nil {
751-
log.Println("[ERROR] RecoverCertificate: Error decoding PFX", err.Error())
752-
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")
855+
}
856+
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, "&")
753882
}
754883

755-
log.Println("[DEBUG] RecoverCertificate: Decoding PFX chain")
756-
priv, leaf, chain, err := pkcs12.DecodeChain(pfxDer, rca.Password)
884+
keyfactorAPIStruct := &request{
885+
Method: "PUT",
886+
Endpoint: endpoint,
887+
Headers: headers,
888+
Payload: req,
889+
}
890+
891+
resp, err := c.sendRequest(keyfactorAPIStruct)
757892
if err != nil {
758-
log.Println("[ERROR] RecoverCertificate: Error decoding PFX chain", err.Error())
759-
return nil, nil, nil, err
893+
return err
760894
}
761895

762-
log.Println("[INFO] Recovered certificate successfully")
763-
log.Println("[DEBUG] RecoverCertificate: ", leaf, chain)
764-
return priv, leaf, chain, nil
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)
899+
}
900+
901+
return nil
765902
}
766903

767904
// createSubject builds the certificate subject string from a passed CertificateSubject argument.

0 commit comments

Comments
 (0)