/**
 *    Copyright (c) 2022-2023 Project CHIP Authors
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

/**
 * Utilities for working with Matter certificates.
 */

#import <Foundation/Foundation.h>

#import <Matter/MTRDefines.h>

NS_ASSUME_NONNULL_BEGIN

@protocol MTRKeypair;

MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1))
@interface MTRCertificates : NSObject

- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;

/**
 * Create a root (self-signed) X.509 DER encoded certificate that has the
 * right fields to be a valid Matter root certificate.
 *
 * If issuerID is not nil, it's unsignedLongLongValue will be used for the
 * matter-rcac-id attribute in the subject distinguished name of the resulting
 * certificate.
 *
 * If issuerID is nil, a random value will be generated for matter-rcac-id.
 *
 * If fabricID is not nil, it will be included in the subject DN of the
 * certificate.  In this case it must be a valid Matter fabric id.
 *
 * validityPeriod specifies when the certificate will be valid. Note that
 * there is currently no mechanism available in Matter to update or rotate
 * the root certificate of a fabric installed on a device. A certificate with
 * no expiration time can be created by specifying [NSDate distantFuture] for
 * the end of the period.
 *
 * On failure returns nil and if "error" is not null sets *error to the relevant
 * error.
 */
+ (MTRCertificateDERBytes _Nullable)createRootCertificate:(id<MTRKeypair>)keypair
                                                 issuerID:(NSNumber * _Nullable)issuerID
                                                 fabricID:(NSNumber * _Nullable)fabricID
                                           validityPeriod:(NSDateInterval *)validityPeriod
                                                    error:(NSError * __autoreleasing _Nullable * _Nullable)error
    MTR_AVAILABLE(ios(16.6), macos(13.5), watchos(9.6), tvos(16.6));

/**
 * As above, but defaults to no expiration time.
 */
+ (MTRCertificateDERBytes _Nullable)createRootCertificate:(id<MTRKeypair>)keypair
                                                 issuerID:(NSNumber * _Nullable)issuerID
                                                 fabricID:(NSNumber * _Nullable)fabricID
                                                    error:(NSError * __autoreleasing _Nullable * _Nullable)error
    MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4));

/**
 * Create an intermediate X.509 DER encoded certificate that has the
 * right fields to be a valid Matter intermediate certificate.
 *
 * If issuerID is not nil, it's unsignedLongLongValue will be used for the
 * matter-icac-id attribute in the subject distinguished name of the resulting
 * certificate.
 *
 * If issuerID is nil, a random value will be generated for matter-icac-id.
 *
 * If fabricID is not nil, it will be included in the subject DN of the
 * certificate.  In this case it must be a valid Matter fabric id.
 *
 * validityPeriod specifies when the certificate will be valid. A certificate
 * with no expiration time can be created by specifying [NSDate distantFuture]
 * for the end of the period.
 *
 * On failure returns nil and if "error" is not null sets *error to the relevant
 * error.
 */
+ (MTRCertificateDERBytes _Nullable)createIntermediateCertificate:(id<MTRKeypair>)rootKeypair
                                                  rootCertificate:(MTRCertificateDERBytes)rootCertificate
                                            intermediatePublicKey:(SecKeyRef)intermediatePublicKey
                                                         issuerID:(NSNumber * _Nullable)issuerID
                                                         fabricID:(NSNumber * _Nullable)fabricID
                                                   validityPeriod:(NSDateInterval *)validityPeriod
                                                            error:(NSError * __autoreleasing _Nullable * _Nullable)error
    MTR_AVAILABLE(ios(16.6), macos(13.5), watchos(9.6), tvos(16.6));

/**
 * As above, but defaults to no expiration time.
 */
+ (MTRCertificateDERBytes _Nullable)createIntermediateCertificate:(id<MTRKeypair>)rootKeypair
                                                  rootCertificate:(MTRCertificateDERBytes)rootCertificate
                                            intermediatePublicKey:(SecKeyRef)intermediatePublicKey
                                                         issuerID:(NSNumber * _Nullable)issuerID
                                                         fabricID:(NSNumber * _Nullable)fabricID
                                                            error:(NSError * __autoreleasing _Nullable * _Nullable)error
    MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4));

/**
 * Create an X.509 DER encoded certificate that has the
 * right fields to be a valid Matter operational certificate.
 *
 * signingKeypair and signingCertificate are the root or intermediate that is
 * signing the operational certificate.
 *
 * nodeID and fabricID are expected to be 64-bit unsigned integers.
 *
 * nodeID must be a valid Matter operational node id.
 *
 * fabricID must be a valid Matter fabric id.
 *
 * caseAuthenticatedTags may be nil to indicate no CASE Authenticated Tags
 * should be used.  If caseAuthenticatedTags is not nil, it must contain at most
 * 3 numbers, which are expected to be 32-bit unsigned Case Authenticated Tag
 * values.
 *
 * validityPeriod specifies when the certificate will be valid. A certificate
 * with no expiration time can be created by specifying [NSDate distantFuture]
 * for the end of the period.
 *
 * On failure returns nil and if "error" is not null sets *error to the relevant
 * error.
 */
+ (MTRCertificateDERBytes _Nullable)createOperationalCertificate:(id<MTRKeypair>)signingKeypair
                                              signingCertificate:(MTRCertificateDERBytes)signingCertificate
                                            operationalPublicKey:(SecKeyRef)operationalPublicKey
                                                        fabricID:(NSNumber *)fabricID
                                                          nodeID:(NSNumber *)nodeID
                                           caseAuthenticatedTags:(NSSet<NSNumber *> * _Nullable)caseAuthenticatedTags
                                                  validityPeriod:(NSDateInterval *)validityPeriod
                                                           error:(NSError * __autoreleasing _Nullable * _Nullable)error
    MTR_AVAILABLE(ios(16.6), macos(13.5), watchos(9.6), tvos(16.6));

/**
 * As above, but defaults to no expiration time.
 */
+ (MTRCertificateDERBytes _Nullable)createOperationalCertificate:(id<MTRKeypair>)signingKeypair
                                              signingCertificate:(MTRCertificateDERBytes)signingCertificate
                                            operationalPublicKey:(SecKeyRef)operationalPublicKey
                                                        fabricID:(NSNumber *)fabricID
                                                          nodeID:(NSNumber *)nodeID
                                           caseAuthenticatedTags:(NSSet<NSNumber *> * _Nullable)caseAuthenticatedTags
                                                           error:(NSError * __autoreleasing _Nullable * _Nullable)error
    MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4));

/**
 * Check whether the given keypair's public key matches the given certificate's
 * public key.  The certificate is expected to be an X.509 DER encoded
 * certificate.
 *
 * Will return NO on failures to extract public keys from the objects.
 */
+ (BOOL)keypair:(id<MTRKeypair>)keypair matchesCertificate:(NSData *)certificate;

/**
 * Check whether two X.509 DER encoded certificates are equivalent, in the sense
 * of having the same public key and the same subject DN.  Returns NO if public
 * keys or subject DNs cannot be extracted from the certificates.
 */
+ (BOOL)isCertificate:(MTRCertificateDERBytes)certificate1 equalTo:(MTRCertificateDERBytes)certificate2;

/**
 * Generate a PKCS#10 certificate signing request from a MTRKeypair.  This can
 * then be used to request an operational or ICA certificate from an external
 * certificate authority.
 *
 * The CSR will have the subject OU DN set to 'CSA', because omitting all
 * identifying information altogether often trips up CSR parsing code.  The CA
 * being used should expect this and ignore the request subject, producing a
 * subject that matches the rules for Matter certificates.
 *
 * On failure returns nil and if "error" is not null sets *error to the relevant
 * error.
 */
+ (MTRCSRDERBytes _Nullable)createCertificateSigningRequest:(id<MTRKeypair>)keypair
                                                      error:(NSError * __autoreleasing _Nullable * _Nullable)error;

/**
 * Convert the given X.509v3 DER encoded certificate to the Matter certificate
 * format.
 *
 * Returns nil if the conversion fails (e.g. if the input data cannot be parsed
 * as a DER encoded X.509 certificate, or if the certificate cannot be
 * represented in the Matter certificate format).
 */
+ (MTRCertificateTLVBytes _Nullable)convertX509Certificate:(MTRCertificateDERBytes)x509Certificate;

/**
 * Convert the given Matter TLV encoded certificate to the X.509v3 DER encoded
 * format.
 *
 * Returns nil if the conversion fails (e.g. if the input data cannot be parsed
 * as a Matter TLV encoded certificate, or if the certificate cannot be
 * represented in the X.509v3 DER format).
 */
+ (MTRCertificateDERBytes _Nullable)convertMatterCertificate:(MTRCertificateTLVBytes)matterCertificate
    MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4));

/**
 * Extract the public key from the given PKCS#10 certificate signing request.
 * This is the public key that a certificate issued in response to the request
 * would need to have.
 */
+ (NSData * _Nullable)publicKeyFromCSR:(MTRCSRDERBytes)csr
                                 error:(NSError * __autoreleasing _Nullable * _Nullable)error
    MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4));

@end

@interface MTRCertificates (Deprecated)

+ (nullable NSData *)generateRootCertificate:(id<MTRKeypair>)keypair
                                    issuerId:(nullable NSNumber *)issuerId
                                    fabricId:(nullable NSNumber *)fabricId
                                       error:(NSError * __autoreleasing _Nullable * _Nullable)error
    MTR_DEPRECATED("Please use createRootCertificate:issuerID:fabricID:error:", ios(16.1, 16.4), macos(13.0, 13.3),
        watchos(9.1, 9.4), tvos(16.1, 16.4));

+ (nullable NSData *)generateIntermediateCertificate:(id<MTRKeypair>)rootKeypair
                                     rootCertificate:(NSData *)rootCertificate
                               intermediatePublicKey:(SecKeyRef)intermediatePublicKey
                                            issuerId:(nullable NSNumber *)issuerId
                                            fabricId:(nullable NSNumber *)fabricId
                                               error:(NSError * __autoreleasing _Nullable * _Nullable)error
    MTR_DEPRECATED("Please use createIntermediateCertificate:rootCertificate:intermediatePublicKey:issuerID:fabricID:error:",
        ios(16.1, 16.4), macos(13.0, 13.3), watchos(9.1, 9.4), tvos(16.1, 16.4));

+ (nullable NSData *)generateOperationalCertificate:(id<MTRKeypair>)signingKeypair
                                 signingCertificate:(NSData *)signingCertificate
                               operationalPublicKey:(SecKeyRef)operationalPublicKey
                                           fabricId:(NSNumber *)fabricId
                                             nodeId:(NSNumber *)nodeId
                              caseAuthenticatedTags:(NSArray<NSNumber *> * _Nullable)caseAuthenticatedTags
                                              error:(NSError * __autoreleasing _Nullable * _Nullable)error
    MTR_DEPRECATED(
        "Plase use "
        "createOperationalCertificate:signingCertificate:operationalPublicKey:fabricID:nodeID:caseAuthenticatedTags:error:",
        ios(16.1, 16.4), macos(13.0, 13.3), watchos(9.1, 9.4), tvos(16.1, 16.4));

+ (nullable NSData *)generateCertificateSigningRequest:(id<MTRKeypair>)keypair
                                                 error:(NSError * __autoreleasing _Nullable * _Nullable)error
    MTR_DEPRECATED("Please use createCertificateSigningRequest:error:", ios(16.1, 16.4), macos(13.0, 13.3), watchos(9.1, 9.4),
        tvos(16.1, 16.4));

@end

NS_ASSUME_NONNULL_END
